From c46d336480a2c69b0e7aa2df1ed000874b59b1cb Mon Sep 17 00:00:00 2001 From: andi-huber Date: Fri, 20 Mar 2026 08:16:00 +0100 Subject: [PATCH 01/15] CAUSEWAY-3975: Open Telemetry Integration initial commit Task-Url: https://issues.apache.org/jira/browse/CAUSEWAY-3975 --- bom/pom.xml | 12 +++ commons/src/main/java/module-info.java | 2 + .../CausewayObservationInternal.java | 76 +++++++++++++++++++ .../CausewayModuleCoreMetamodel.java | 14 +++- .../runtime/CausewayModuleCoreRuntime.java | 4 +- ...rvice.java => XrayInitializerService.java} | 34 ++------- .../CausewayModuleCoreRuntimeServices.java | 11 +++ .../session/InteractionServiceDefault.java | 50 ++++++++---- .../CausewayModuleViewerWicketModel.java | 14 +++- 9 files changed, 170 insertions(+), 47 deletions(-) create mode 100644 commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java rename core/runtime/src/main/java/org/apache/causeway/core/runtime/events/{MetamodelEventService.java => XrayInitializerService.java} (58%) diff --git a/bom/pom.xml b/bom/pom.xml index 1d7abd049f0..cd8c95f4445 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -1803,6 +1803,18 @@ identified ${jdom.version} + + org.springframework.boot + spring-boot-starter-opentelemetry + ${spring-boot.version} + + + org.jetbrains + annotations + + + + org.springframework.boot spring-boot-starter-quartz diff --git a/commons/src/main/java/module-info.java b/commons/src/main/java/module-info.java index 398b9432e33..a5d7963e405 100644 --- a/commons/src/main/java/module-info.java +++ b/commons/src/main/java/module-info.java @@ -50,6 +50,7 @@ exports org.apache.causeway.commons.internal.html; exports org.apache.causeway.commons.internal.image; exports org.apache.causeway.commons.internal.ioc; + exports org.apache.causeway.commons.internal.observation; exports org.apache.causeway.commons.internal.os; exports org.apache.causeway.commons.internal.primitives; exports org.apache.causeway.commons.internal.proxy; @@ -67,6 +68,7 @@ requires transitive tools.jackson.core; requires transitive tools.jackson.databind; requires transitive tools.jackson.module.jakarta.xmlbind; + requires transitive micrometer.observation; requires transitive org.jdom2; requires transitive org.jspecify; requires transitive org.jsoup; diff --git a/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java b/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java new file mode 100644 index 00000000000..f4387ed04d9 --- /dev/null +++ b/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java @@ -0,0 +1,76 @@ +/* + * 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.commons.internal.observation; + +import java.util.Optional; + +import org.springframework.util.StringUtils; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; + +/** + * Holder of {@link ObservationRegistry} which comes as a dependency of spring-context. + * + * @apiNote each Causeway module can have its own, using qualifiers and bean factory methods, e.g.: + *
+ * @Bean("causeway-metamodel")
+ * public CausewayObservationInternal causewayObservationInternal(
+ *   Optional observationRegistryOpt) {
+ *   return new CausewayObservationInternal(observationRegistryOpt, "causeway-metamodel");
+ * }
+ *  
+ */ +public record CausewayObservationInternal( + ObservationRegistry observationRegistry, + String module) { + + public CausewayObservationInternal( + final Optional observationRegistryOpt, + final String module) { + this(observationRegistryOpt.orElse(ObservationRegistry.NOOP), module); + } + + public CausewayObservationInternal { + observationRegistry = observationRegistry!=null + ? observationRegistry + : ObservationRegistry.NOOP; + module = StringUtils.hasText(module) ? module : "unknown_module"; + } + + public boolean isNoop() { + return observationRegistry.isNoop(); + } + + public Observation createNotStarted(final Class bean, final String name) { + return Observation.createNotStarted(name, observationRegistry) + .lowCardinalityKeyValue("module", module) + .highCardinalityKeyValue("bean", bean.getSimpleName()); + } + + @FunctionalInterface + public interface ObservationProvider { + Observation get(String name); + } + + public ObservationProvider provider(final Class bean) { + return name->createNotStarted(bean, name); + } + +} diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java index f0b1049c17e..4f6b1a6b210 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Stream; import jakarta.inject.Provider; @@ -42,6 +43,7 @@ import org.apache.causeway.commons.functional.Either; import org.apache.causeway.commons.functional.Railway; import org.apache.causeway.commons.functional.Try; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; import org.apache.causeway.commons.semantics.CollectionSemantics; import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.config.CausewayModuleCoreConfig; @@ -116,7 +118,9 @@ import org.apache.causeway.core.metamodel.valuetypes.ValueSemanticsResolverDefault; import org.apache.causeway.core.security.CausewayModuleCoreSecurity; -@Configuration +import io.micrometer.observation.ObservationRegistry; + +@Configuration(proxyBeanMethods = false) @Import({ // Modules CausewayModuleApplib.class, @@ -200,7 +204,7 @@ // standalone validators LogicalTypeMalformedValidator.class, - + // menubar contributions MetamodelInspectMenu.class }) @@ -261,4 +265,10 @@ public ValueCodec valueCodec( return new ValueCodec(bookmarkService, valueSemanticsResolverProvider); } + @Bean("causeway-metamodel") + public CausewayObservationInternal causewayObservationInternal( + final Optional observationRegistryOpt) { + return new CausewayObservationInternal(observationRegistryOpt, "causeway-metamodel"); + } + } diff --git a/core/runtime/src/main/java/org/apache/causeway/core/runtime/CausewayModuleCoreRuntime.java b/core/runtime/src/main/java/org/apache/causeway/core/runtime/CausewayModuleCoreRuntime.java index 442e0008e44..220ff51c8c3 100644 --- a/core/runtime/src/main/java/org/apache/causeway/core/runtime/CausewayModuleCoreRuntime.java +++ b/core/runtime/src/main/java/org/apache/causeway/core/runtime/CausewayModuleCoreRuntime.java @@ -23,7 +23,7 @@ import org.apache.causeway.core.interaction.CausewayModuleCoreInteraction; import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel; -import org.apache.causeway.core.runtime.events.MetamodelEventService; +import org.apache.causeway.core.runtime.events.XrayInitializerService; import org.apache.causeway.core.transaction.CausewayModuleCoreTransaction; @Configuration @@ -34,7 +34,7 @@ CausewayModuleCoreTransaction.class, // @Service's - MetamodelEventService.class, + XrayInitializerService.class, }) public class CausewayModuleCoreRuntime { diff --git a/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/MetamodelEventService.java b/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/XrayInitializerService.java similarity index 58% rename from core/runtime/src/main/java/org/apache/causeway/core/runtime/events/MetamodelEventService.java rename to core/runtime/src/main/java/org/apache/causeway/core/runtime/events/XrayInitializerService.java index d02a988f772..4320e218ece 100644 --- a/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/MetamodelEventService.java +++ b/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/XrayInitializerService.java @@ -18,49 +18,31 @@ */ package org.apache.causeway.core.runtime.events; -import jakarta.annotation.Priority; -import jakarta.inject.Inject; import jakarta.inject.Named; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; -import org.apache.causeway.applib.annotation.PriorityPrecedence; -import org.apache.causeway.applib.events.metamodel.MetamodelEvent; +import org.apache.causeway.applib.events.metamodel.MetamodelListener; import org.apache.causeway.applib.services.confview.ConfigurationViewService; -import org.apache.causeway.applib.services.eventbus.EventBusService; import org.apache.causeway.core.runtime.CausewayModuleCoreRuntime; -/** - * - * @since 2.0 - * @implNote Listeners to runtime events can only reliably receive these after the - * post-construct phase has finished and before the pre-destroy phase has begun. - */ @Service -@Named(CausewayModuleCoreRuntime.NAMESPACE + ".MetamodelEventService") -@Priority(PriorityPrecedence.MIDPOINT) -@Qualifier("Default") -public class MetamodelEventService { - - @Inject - private EventBusService eventBusService; +@Named(CausewayModuleCoreRuntime.NAMESPACE + ".XrayInitializerService") +public class XrayInitializerService implements MetamodelListener { @Autowired(required = false) private ConfigurationViewService configurationService; - public void fireBeforeMetamodelLoading() { - + @Override + public void onMetamodelAboutToBeLoaded() { if(configurationService!=null) { _Xray.addConfiguration(configurationService); } - - eventBusService.post(MetamodelEvent.BEFORE_METAMODEL_LOADING); } - public void fireAfterMetamodelLoaded() { - eventBusService.post(MetamodelEvent.AFTER_METAMODEL_LOADED); + @Override + public void onMetamodelLoaded() { + // no-op } - } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/CausewayModuleCoreRuntimeServices.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/CausewayModuleCoreRuntimeServices.java index bee6e2db2ce..ae003667556 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/CausewayModuleCoreRuntimeServices.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/CausewayModuleCoreRuntimeServices.java @@ -18,6 +18,8 @@ */ package org.apache.causeway.core.runtimeservices; +import java.util.Optional; + import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -29,6 +31,7 @@ import org.apache.causeway.applib.annotation.PriorityPrecedence; import org.apache.causeway.applib.services.bookmark.HmacAuthority; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; import org.apache.causeway.core.codegen.bytebuddy.CausewayModuleCoreCodegenByteBuddy; import org.apache.causeway.core.runtime.CausewayModuleCoreRuntime; import org.apache.causeway.core.runtimeservices.bookmarks.BookmarkServiceDefault; @@ -73,6 +76,8 @@ import org.apache.causeway.core.runtimeservices.xml.XmlServiceDefault; import org.apache.causeway.core.runtimeservices.xmlsnapshot.XmlSnapshotServiceDefault; +import io.micrometer.observation.ObservationRegistry; + @Configuration(proxyBeanMethods = false) @Import({ // Modules @@ -151,4 +156,10 @@ public HmacAuthority fallbackHmacAuthority() { } } + @Bean("causeway-runtimeservices") + public CausewayObservationInternal causewayObservationInternal( + final Optional observationRegistryOpt) { + return new CausewayObservationInternal(observationRegistryOpt, "causeway-runtimeservices"); + } + } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java index 810a262b682..10c70fe6869 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java @@ -30,6 +30,8 @@ import jakarta.inject.Named; import jakarta.inject.Provider; +import org.jspecify.annotations.NonNull; + import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.event.ContextRefreshedEvent; @@ -38,8 +40,10 @@ import org.apache.causeway.applib.annotation.PriorityPrecedence; import org.apache.causeway.applib.annotation.Programmatic; +import org.apache.causeway.applib.events.metamodel.MetamodelEvent; import org.apache.causeway.applib.services.clock.ClockService; import org.apache.causeway.applib.services.command.Command; +import org.apache.causeway.applib.services.eventbus.EventBusService; import org.apache.causeway.applib.services.iactn.Interaction; import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionLayer; @@ -57,17 +61,17 @@ import org.apache.causeway.commons.internal.debug._Probe; import org.apache.causeway.commons.internal.debug.xray.XrayUi; import org.apache.causeway.commons.internal.exceptions._Exceptions; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal.ObservationProvider; import org.apache.causeway.core.interaction.scope.InteractionScopeBeanFactoryPostProcessor; import org.apache.causeway.core.interaction.scope.InteractionScopeLifecycleHandler; import org.apache.causeway.core.interaction.session.CausewayInteraction; import org.apache.causeway.core.metamodel.services.publishing.CommandPublisher; import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; -import org.apache.causeway.core.runtime.events.MetamodelEventService; import org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices; import org.apache.causeway.core.runtimeservices.transaction.TransactionServiceSpring; import org.apache.causeway.core.security.authentication.InteractionContextFactory; -import org.jspecify.annotations.NonNull; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -93,7 +97,8 @@ public class InteractionServiceDefault // ThreadLocal would be considered bad practice and instead should be managed using the TransactionSynchronization mechanism. final ThreadLocal> interactionLayerStack = ThreadLocal.withInitial(Stack::new); - final MetamodelEventService runtimeEventService; + final EventBusService eventBusService; + final ObservationProvider observationProvider; final Provider specificationLoaderProvider; final ServiceInjector serviceInjector; @@ -108,7 +113,9 @@ public class InteractionServiceDefault @Inject public InteractionServiceDefault( - final MetamodelEventService runtimeEventService, + final EventBusService eventBusService, + @Qualifier("causeway-runtimeservices") + final CausewayObservationInternal observation, final Provider specificationLoaderProvider, final ServiceInjector serviceInjector, final TransactionServiceSpring transactionServiceSpring, @@ -116,7 +123,8 @@ public InteractionServiceDefault( final Provider commandPublisherProvider, final ConfigurableBeanFactory beanFactory, final InteractionIdGenerator interactionIdGenerator) { - this.runtimeEventService = runtimeEventService; + this.eventBusService = eventBusService; + this.observationProvider = observation.provider(getClass()); this.specificationLoaderProvider = specificationLoaderProvider; this.serviceInjector = serviceInjector; this.transactionServiceSpring = transactionServiceSpring; @@ -124,19 +132,34 @@ public InteractionServiceDefault( this.commandPublisherProvider = commandPublisherProvider; this.beanFactory = beanFactory; this.interactionIdGenerator = interactionIdGenerator; - this.interactionScopeLifecycleHandler = InteractionScopeBeanFactoryPostProcessor.lookupScope(beanFactory); } @EventListener public void init(final ContextRefreshedEvent event) { - log.info("Initialising Causeway System"); log.info("working directory: {}", new File(".").getAbsolutePath()); - runtimeEventService.fireBeforeMetamodelLoading(); + observationProvider.get("Initialising Causeway System") + .observe(()->{ + observationProvider.get("Notify BEFORE_METAMODEL_LOADING Listeners") + .observe(()->{ + eventBusService.post(MetamodelEvent.BEFORE_METAMODEL_LOADING); + }); + + observationProvider.get("Initialising Causeway Metamodel") + .observe(()->{ + initMetamodel(specificationLoaderProvider.get()); + }); + + observationProvider.get("Notify AFTER_METAMODEL_LOADED Listeners") + .observe(()->{ + eventBusService.post(MetamodelEvent.AFTER_METAMODEL_LOADED); + }); + }); + } - var specificationLoader = specificationLoaderProvider.get(); + private void initMetamodel(final SpecificationLoader specificationLoader) { var taskList = _ConcurrentTaskList.named("CausewayInteractionFactoryDefault Init") .addRunnable("SpecificationLoader::createMetaModel", specificationLoader::createMetaModel) @@ -161,9 +184,6 @@ public void init(final ContextRefreshedEvent event) { //throw _Exceptions.unrecoverable("Validation FAILED"); } } - - runtimeEventService.fireAfterMetamodelLoaded(); - } @Override @@ -191,10 +211,9 @@ public InteractionLayer openInteraction( .map(currentInteractionContext -> Objects.equals(currentInteractionContext, interactionContextToUse)) .orElse(false); - if(reuseCurrentLayer) { + if(reuseCurrentLayer) // we are done, just return the stack's top return interactionLayerStack.get().peek(); - } var interactionLayer = new InteractionLayer(causewayInteraction, interactionContextToUse); @@ -465,9 +484,8 @@ private void closeInteractionLayerStackDownToStackSize(final int downToStackSize private CausewayInteraction getInternalInteractionElseFail() { var interaction = currentInteractionElseFail(); - if(interaction instanceof CausewayInteraction) { + if(interaction instanceof CausewayInteraction) return (CausewayInteraction) interaction; - } throw _Exceptions.unrecoverable("the framework does not recognize " + "this implementation of an Interaction: %s", interaction.getClass().getName()); } diff --git a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/CausewayModuleViewerWicketModel.java b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/CausewayModuleViewerWicketModel.java index 6db06646573..7e3bca0fbd0 100644 --- a/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/CausewayModuleViewerWicketModel.java +++ b/viewers/wicket/model/src/main/java/org/apache/causeway/viewer/wicket/model/CausewayModuleViewerWicketModel.java @@ -18,18 +18,30 @@ */ package org.apache.causeway.viewer.wicket.model; +import java.util.Optional; + +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; import org.apache.causeway.core.webapp.CausewayModuleCoreWebapp; +import io.micrometer.observation.ObservationRegistry; + /** * @since 1.x {@index} */ -@Configuration +@Configuration(proxyBeanMethods = false) @Import({ // Modules CausewayModuleCoreWebapp.class, }) public class CausewayModuleViewerWicketModel { + + @Bean("causeway-wicketviewer") + public CausewayObservationInternal causewayObservationInternal( + final Optional observationRegistryOpt) { + return new CausewayObservationInternal(observationRegistryOpt, "causeway-wicketviewer"); + } } From 3bd6ff37b091b5f61338e58e632f5cd703988429 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Fri, 20 Mar 2026 22:51:24 +0100 Subject: [PATCH 02/15] CAUSEWAY-3975: work on simplified InteractionService hope to better integrate with micrometer API --- .../services/iactnlayer/InteractionLayer.java | 40 +- .../iactnlayer/InteractionLayerStack.java | 93 +++ .../iactnlayer/InteractionService.java | 125 ++-- .../session/InteractionServiceDefault.java | 134 ++-- .../core/runtimeservices/session/_Xray.java | 22 +- .../InteractionService_forTesting.java | 43 +- .../BackgroundService_IntegTestAbstract.java | 47 +- .../CommandLog_IntegTestAbstract.java | 598 +++++++++--------- .../ExecutionLog_IntegTestAbstract.java | 235 +++---- .../ExecutionOutbox_IntegTestAbstract.java | 21 +- .../AuditTrail_IntegTestAbstract.java | 246 +++---- .../conf/Configuration_usingWicket.java | 13 +- ...CmdExecAuditSessLog_IntegTestAbstract.java | 208 +++--- .../integtest/Layout_Counter_IntegTest.java | 14 +- .../applib/CausewayInteractionHandler.java | 8 +- .../applib/NoPermissionChecks.java | 2 +- .../applib/UserMementoRefiners.java | 3 +- .../AuthenticatedWebSessionForCauseway.java | 152 ++--- .../viewer/integration/RequestCycle2.java | 84 +++ .../integration/SessionAuthenticator.java | 61 ++ .../WebRequestCycleForCauseway.java | 68 +- ...CausewayWicketAjaxRequestListenerUtil.java | 7 +- .../wicketapp/CausewayWicketApplication.java | 8 +- 23 files changed, 1254 insertions(+), 978 deletions(-) create mode 100644 api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayerStack.java create mode 100644 viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java create mode 100644 viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/SessionAuthenticator.java diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayer.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayer.java index 170931239a0..13da3e007fb 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayer.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayer.java @@ -18,36 +18,54 @@ */ package org.apache.causeway.applib.services.iactnlayer; +import org.jspecify.annotations.Nullable; + import org.apache.causeway.applib.services.iactn.Interaction; /** * Binds an {@link Interaction} ("what" is being executed) with * an {@link InteractionContext} ("who" is executing, "when" and "where"). * - *

- * {@link InteractionLayer}s are so called because they may be nested (held in a stack). For example the + *

{@link InteractionLayer}s are so called because they may be nested (held in a stack). For example the * {@link org.apache.causeway.applib.services.sudo.SudoService} creates a new temporary layer with a different * {@link InteractionContext#getUser() user}, while fixtures that mock the clock switch out the * {@link InteractionContext#getClock() clock}. - *

* - *

- * The stack of layers is per-thread, managed by {@link InteractionService} as a thread-local). - *

+ *

The stack of layers is per-thread, managed by {@link InteractionService} as a thread-local). * * @since 2.0 {@index} */ public record InteractionLayer( + @Nullable InteractionLayer parent, /** - * Current thread's {@link Interaction} : "what" is being executed + * Current thread's {@link Interaction} : WHAT is being executed */ Interaction interaction, /** - * "who" is performing this {@link #getInteraction()}, also - * "when" and "where". + * WHO is performing this {@link #getInteraction()}, also + * WHEN and WHERE. */ - InteractionContext interactionContext - ) { + InteractionContext interactionContext) { + + public boolean isRoot() { + return parent==null; + } + + public int parentCount() { + return parent!=null + ? 1 + parent.parentCount() + : 0; + } + + public int totalLayerCount() { + return 1 + parentCount(); + } + + public InteractionLayer rootLayer() { + return parent!=null + ? parent.rootLayer() + : this; + } } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayerStack.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayerStack.java new file mode 100644 index 00000000000..3791d22d494 --- /dev/null +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayerStack.java @@ -0,0 +1,93 @@ +/* + * 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.iactnlayer; + +import java.util.Optional; +import java.util.function.Predicate; + +import org.jspecify.annotations.Nullable; + +import org.apache.causeway.applib.services.iactn.Interaction; + +public final class InteractionLayerStack { + + // TODO: reading the javadoc for TransactionSynchronizationManager and looking at the implementations + // of TransactionSynchronization (in particular SpringSessionSynchronization), I suspect that this + // ThreadLocal would be considered bad practice and instead should be managed using the TransactionSynchronization mechanism. + private final ThreadLocal threadLocalLayer = new ThreadLocal<>(); + + public Optional currentLayer() { + return Optional.ofNullable(threadLocalLayer.get()); + } + + public InteractionLayer push( + final Interaction interaction, + final InteractionContext interactionContext) { + var parent = currentLayer().orElse(null); + var newLayer = new InteractionLayer(parent, interaction, interactionContext); + threadLocalLayer.set(newLayer); + return newLayer; + } + + public void clear() { + threadLocalLayer.remove(); + } + + public boolean isEmpty() { + return threadLocalLayer.get()==null; + } + + public int size() { + return currentLayer() + .map(InteractionLayer::totalLayerCount) + .orElse(0); + } + + @Nullable + public InteractionLayer peek() { + return threadLocalLayer.get(); + } + + @Nullable + public InteractionLayer pop() { + var current = threadLocalLayer.get(); + return set(current != null + ? current.parent() + : null); + } + + public void popWhile(final Predicate condition) { + while(!isEmpty()) { + if(!condition.test(peek())) return; + pop(); + } + } + + // -- HELPER + + private InteractionLayer set(@Nullable final InteractionLayer layer) { + if(layer != null) { + threadLocalLayer.set(layer); + } else { + threadLocalLayer.remove(); + } + return layer; + } + +} diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java index d133392bb42..60845096603 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java @@ -19,12 +19,13 @@ package org.apache.causeway.applib.services.iactnlayer; import java.util.concurrent.Callable; +import java.util.function.Consumer; + +import org.jspecify.annotations.NonNull; import org.apache.causeway.commons.functional.ThrowingRunnable; import org.apache.causeway.commons.functional.Try; -import org.jspecify.annotations.NonNull; - /** * A low-level service to programmatically create a short-lived interaction or session. * @@ -55,42 +56,6 @@ */ public interface InteractionService extends InteractionLayerTracker { - /** - * If present, reuses the current top level {@link InteractionLayer}, otherwise creates a new - * anonymous one. - * - * @see #openInteraction(InteractionContext) - */ - InteractionLayer openInteraction(); - - /** - * Returns a new or reused {@link InteractionLayer} that is a holder of the {@link InteractionContext} - * on top of the current thread's interaction layer stack. - * - *

- * If available reuses an existing {@link InteractionContext}, otherwise creates a new one. - *

- * - *

- * The {@link InteractionLayer} represents a user's span of activities interacting with - * the application. These can be stacked (usually temporarily), for example for a sudo - * session or to mock the clock. The stack is later closed using {@link #closeInteractionLayers()}. - *

- * - * @param interactionContext - * - * @apiNote if the current {@link InteractionLayer} (if any) has an {@link InteractionContext} that - * equals that of the given one, as an optimization, no new layer is pushed onto the stack; - * instead the current one is returned - */ - InteractionLayer openInteraction( - @NonNull InteractionContext interactionContext); - - /** - * closes all open {@link InteractionLayer}(s) as stacked on the current thread - */ - void closeInteractionLayers(); - /** * @return whether the calling thread is within the context of an open {@link InteractionLayer} */ @@ -205,33 +170,69 @@ default Try runAnonymousAndCatch( } /** - * Primarily for testing, closes the current interaction and opens a new one. - * - *

- * In tests, this is a good way to simulate multiple interactions within a scenario. If you use the popular - * given/when/then structure, consider using at the end of each "given" or "when" block. - *

- * - * @see #closeInteractionLayers() - * @see #openInteraction() - * @see #nextInteraction(InteractionContext) + * closes all open {@link InteractionLayer}(s) as stacked on the current thread */ - default InteractionLayer nextInteraction() { - closeInteractionLayers(); - return openInteraction(); + void closeInteractionLayers(); + + public interface TestSupport { + T model(); + /** + * Primarily for testing, closes the current interaction and opens a new one. + * + *

+ * In tests, this is a good way to simulate multiple interactions within a scenario. If you use the popular + * given/when/then structure, consider using at the end of each "given" or "when" block. + *

+ * + * @see #closeInteractionLayers() + * @see #openInteraction() + * @see #nextInteraction(InteractionContext) + */ + void nextInteraction(final Consumer callback); + /** + * Primarily for testing, closes the current interaction and opens a new one with the specified + * {@link InteractionContext}. + * + * @see #closeInteractionLayers() + * @see #openInteraction(InteractionContext) + * @see #nextInteraction() + */ + void nextInteraction(final InteractionContext interactionContext, final Consumer callback); + /** + * If present, reuses the current top level {@link InteractionLayer}, otherwise creates a new + * anonymous one. + * + * @see #openInteraction(InteractionContext) + */ + InteractionLayer openInteraction(); + /** + * Returns a new or reused {@link InteractionLayer} that is a holder of the {@link InteractionContext} + * on top of the current thread's interaction layer stack. + * + *

+ * If available reuses an existing {@link InteractionContext}, otherwise creates a new one. + *

+ * + *

+ * The {@link InteractionLayer} represents a user's span of activities interacting with + * the application. These can be stacked (usually temporarily), for example for a sudo + * session or to mock the clock. The stack is later closed using {@link #closeInteractionLayers()}. + *

+ * + * @param interactionContext + * + * @apiNote if the current {@link InteractionLayer} (if any) has an {@link InteractionContext} that + * equals that of the given one, as an optimization, no new layer is pushed onto the stack; + * instead the current one is returned + */ + InteractionLayer openInteraction( + @NonNull InteractionContext interactionContext); } - /** - * Primarily for testing, closes the current interaction and opens a new one with the specified - * {@link InteractionContext}. - * - * @see #closeInteractionLayers() - * @see #openInteraction(InteractionContext) - * @see #nextInteraction() - */ - default InteractionLayer nextInteraction(final InteractionContext interactionContext) { - closeInteractionLayers(); - return openInteraction(interactionContext); + TestSupport testSupport(T model); + + default TestSupport testSupport() { + return testSupport(null); } } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java index 10c70fe6869..7f2f854fa20 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java @@ -21,9 +21,9 @@ import java.io.File; import java.util.Objects; import java.util.Optional; -import java.util.Stack; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.function.Consumer; import jakarta.annotation.Priority; import jakarta.inject.Inject; @@ -47,6 +47,7 @@ import org.apache.causeway.applib.services.iactn.Interaction; import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionLayer; +import org.apache.causeway.applib.services.iactnlayer.InteractionLayerStack; import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker; import org.apache.causeway.applib.services.iactnlayer.InteractionService; import org.apache.causeway.applib.services.inject.ServiceInjector; @@ -55,7 +56,6 @@ import org.apache.causeway.applib.util.schema.InteractionDtoUtils; import org.apache.causeway.applib.util.schema.InteractionsDtoUtils; import org.apache.causeway.commons.functional.ThrowingRunnable; -import org.apache.causeway.commons.internal.base._Casts; import org.apache.causeway.commons.internal.concurrent._ConcurrentContext; import org.apache.causeway.commons.internal.concurrent._ConcurrentTaskList; import org.apache.causeway.commons.internal.debug._Probe; @@ -72,7 +72,10 @@ import org.apache.causeway.core.runtimeservices.transaction.TransactionServiceSpring; import org.apache.causeway.core.security.authentication.InteractionContextFactory; +import lombok.Getter; import lombok.SneakyThrows; +import lombok.Value; +import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; /** @@ -92,10 +95,7 @@ public class InteractionServiceDefault InteractionService, InteractionLayerTracker { - // TODO: reading the javadoc for TransactionSynchronizationManager and looking at the implementations - // of TransactionSynchronization (in particular SpringSessionSynchronization), I suspect that this - // ThreadLocal would be considered bad practice and instead should be managed using the TransactionSynchronization mechanism. - final ThreadLocal> interactionLayerStack = ThreadLocal.withInitial(Stack::new); + final InteractionLayerStack layerStack = new InteractionLayerStack(); final EventBusService eventBusService; final ObservationProvider observationProvider; @@ -159,6 +159,7 @@ public void init(final ContextRefreshedEvent event) { }); } + //TODO this is a metamodel concern, why is it in runtime services? private void initMetamodel(final SpecificationLoader specificationLoader) { var taskList = _ConcurrentTaskList.named("CausewayInteractionFactoryDefault Init") @@ -188,69 +189,63 @@ private void initMetamodel(final SpecificationLoader specificationLoader) { @Override public int getInteractionLayerCount() { - return interactionLayerStack.get().size(); + return layerStack.size(); } - @Override - public InteractionLayer openInteraction() { + private InteractionLayer openInteraction() { return currentInteractionLayer() // or else create an anonymous authentication layer .orElseGet(()->openInteraction(InteractionContextFactory.anonymous())); } - @Override - public InteractionLayer openInteraction( - final @NonNull InteractionContext interactionContextToUse) { - - var causewayInteraction = getOrCreateCausewayInteraction(); + private InteractionLayer openInteraction(final @NonNull InteractionContext interactionContextToUse) { // check whether we should reuse any current interactionLayer, // that is, if current authentication and authToUse are equal - var reuseCurrentLayer = currentInteractionContext() .map(currentInteractionContext -> Objects.equals(currentInteractionContext, interactionContextToUse)) .orElse(false); - if(reuseCurrentLayer) // we are done, just return the stack's top - return interactionLayerStack.get().peek(); + return currentInteractionLayerElseFail(); - var interactionLayer = new InteractionLayer(causewayInteraction, interactionContextToUse); + var newInteractionLayer = observationProvider.get("New Interaction Layer") + .highCardinalityKeyValue("stackSize", ""+getInteractionLayerCount()) + .observe(()->{ - interactionLayerStack.get().push(interactionLayer); + var causewayInteraction = currentInteractionLayer() + .map(InteractionLayer::interaction) + .map(it->(CausewayInteraction)it) + .orElseGet(()->new CausewayInteraction(interactionIdGenerator.interactionId())); + var interactionLayer = layerStack.push(causewayInteraction, interactionContextToUse); - if(isAtTopLevel()) { - transactionServiceSpring.onOpen(causewayInteraction); - interactionScopeLifecycleHandler.onTopLevelInteractionOpened(); - } + if(isAtTopLevel()) { + transactionServiceSpring.onOpen(causewayInteraction); + interactionScopeLifecycleHandler.onTopLevelInteractionOpened(); + } + + return interactionLayer; + }); if(log.isDebugEnabled()) { log.debug("new interaction layer created (interactionId={}, total-layers-on-stack={}, {})", currentInteraction().map(Interaction::getInteractionId).orElse(null), - interactionLayerStack.get().size(), + getInteractionLayerCount(), _Probe.currentThreadId()); } if(XrayUi.isXrayEnabled()) { - _Xray.newInteractionLayer(interactionLayerStack.get()); + _Xray.newInteractionLayer(newInteractionLayer); } - return interactionLayer; - } - - private CausewayInteraction getOrCreateCausewayInteraction() { - - final Stack interactionLayers = interactionLayerStack.get(); - return interactionLayers.isEmpty() - ? new CausewayInteraction(interactionIdGenerator.interactionId()) - : _Casts.uncheckedCast(interactionLayers.firstElement().interaction()); + return newInteractionLayer; } @Override public void closeInteractionLayers() { log.debug("about to close the interaction stack (interactionId={}, total-layers-on-stack={}, {})", currentInteraction().map(Interaction::getInteractionId).orElse(null), - interactionLayerStack.get().size(), + layerStack.size(), _Probe.currentThreadId()); // @@ -262,15 +257,12 @@ public void closeInteractionLayers() { @Override public Optional currentInteractionLayer() { - var stack = interactionLayerStack.get(); - return stack.isEmpty() - ? Optional.empty() - : Optional.of(stack.lastElement()); + return layerStack.currentLayer(); } @Override public boolean isInInteraction() { - return !interactionLayerStack.get().isEmpty(); + return !layerStack.isEmpty(); } // -- AUTHENTICATED EXECUTION @@ -281,7 +273,7 @@ public R call( final @NonNull InteractionContext interactionContext, final @NonNull Callable callable) { - final int stackSizeWhenEntering = interactionLayerStack.get().size(); + final int stackSizeWhenEntering = layerStack.size(); openInteraction(interactionContext); try { return callInternal(callable); @@ -302,7 +294,7 @@ public void run( final @NonNull InteractionContext interactionContext, final @NonNull ThrowingRunnable runnable) { - final int stackSizeWhenEntering = interactionLayerStack.get().size(); + final int stackSizeWhenEntering = layerStack.size(); openInteraction(interactionContext); try { runInternal(runnable); @@ -374,8 +366,7 @@ private void runInternal(final @NonNull ThrowingRunnable runnable) { } private void requestRollback(final Throwable cause) { - var stack = interactionLayerStack.get(); - if(stack.isEmpty()) { + if(layerStack.isEmpty()) { // seeing this code-path, when the corresponding runnable/callable // by itself causes the interaction stack to be closed log.warn("unexpected state: missing interaction (layer) on interaction rollback; " @@ -384,12 +375,12 @@ private void requestRollback(final Throwable cause) { cause.getMessage()); return; } - var interaction = _Casts.uncheckedCast(stack.get(0).interaction()); + var interaction = (CausewayInteraction) layerStack.peek().rootLayer().interaction(); transactionServiceSpring.requestRollback(interaction); } private boolean isAtTopLevel() { - return interactionLayerStack.get().size()==1; + return layerStack.size()==1; } @SneakyThrows @@ -454,30 +445,31 @@ private void preInteractionClosed(final CausewayInteraction interaction) { } private void closeInteractionLayerStackDownToStackSize(final int downToStackSize) { + if(layerStack.isEmpty()) return; + if(downToStackSize<0) throw new IllegalArgumentException("required non-negative"); log.debug("about to close interaction stack down to size {} (interactionId={}, total-layers-on-stack={}, {})", downToStackSize, currentInteraction().map(Interaction::getInteractionId).orElse(null), - interactionLayerStack.get().size(), + layerStack.size(), _Probe.currentThreadId()); - var stack = interactionLayerStack.get(); try { - while(stack.size()>downToStackSize) { + layerStack.popWhile(currentLayer->{ + if(!(layerStack.size()>downToStackSize)) return false; if(isAtTopLevel()) { // keep the stack unmodified yet, to allow for callbacks to properly operate - - preInteractionClosed(_Casts.uncheckedCast(stack.peek().interaction())); + preInteractionClosed((CausewayInteraction)currentLayer.interaction()); } - _Xray.closeInteractionLayer(stack); - stack.pop(); - } + _Xray.closeInteractionLayer(currentLayer); + return true; + }); } finally { // preInteractionClosed above could conceivably throw an exception, so we'll tidy up our threadlocal // here to ensure everything is cleaned up if(downToStackSize == 0) { // cleanup thread-local - interactionLayerStack.remove(); + layerStack.clear(); } } } @@ -524,4 +516,36 @@ public void completeAndPublishCurrentCommand() { interaction.clear(); } + @Value + static final class TestSupportImpl implements TestSupport { + @Getter @Accessors(fluent = true) final T model; + final InteractionServiceDefault interactionService; + + @Override + public void nextInteraction(final Consumer callback) { + interactionService.closeInteractionLayers(); + interactionService.openInteraction(); + callback.accept(model); + } + @Override + public void nextInteraction(final InteractionContext interactionContext, final Consumer callback) { + interactionService.closeInteractionLayers(); + interactionService.openInteraction(interactionContext); + callback.accept(model); + } + @Override + public InteractionLayer openInteraction() { + return interactionService.openInteraction(); + } + @Override + public InteractionLayer openInteraction(@NonNull final InteractionContext interactionContext) { + return interactionService.openInteraction(interactionContext); + } + } + + @Override + public TestSupport testSupport(final T model) { + return new TestSupportImpl<>(model, this); + } + } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/_Xray.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/_Xray.java index d040d4c3db8..7a884c5dcc7 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/_Xray.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/_Xray.java @@ -18,8 +18,6 @@ */ package org.apache.causeway.core.runtimeservices.session; -import java.util.Stack; - import org.apache.causeway.applib.services.iactnlayer.InteractionLayer; import org.apache.causeway.commons.internal.debug._XrayEvent; import org.apache.causeway.commons.internal.debug.xray.XrayDataModel; @@ -30,16 +28,15 @@ //@Slf4j final class _Xray { - static void newInteractionLayer(final Stack afterEnter) { + static void newInteractionLayer(final InteractionLayer afterEnter) { - if(!XrayUi.isXrayEnabled()) { + if(!XrayUi.isXrayEnabled()) return; - } // make defensive copies, so can use in another thread - final int authStackSize = afterEnter.size(); - var interactionId = afterEnter.peek().interaction().getInteractionId(); - var executionContext = afterEnter.peek().interactionContext(); + final int authStackSize = afterEnter.totalLayerCount(); + var interactionId = afterEnter.interaction().getInteractionId(); + var executionContext = afterEnter.interactionContext(); _XrayEvent.interactionOpen("open interaction %s", interactionId); @@ -86,14 +83,13 @@ static void newInteractionLayer(final Stack afterEnter) { } - public static void closeInteractionLayer(final Stack beforeClose) { + public static void closeInteractionLayer(final InteractionLayer beforeClose) { - if(!XrayUi.isXrayEnabled()) { + if(!XrayUi.isXrayEnabled()) return; - } - final int authStackSize = beforeClose.size(); - var interactionId = beforeClose.peek().interaction().getInteractionId(); + final int authStackSize = beforeClose.totalLayerCount(); + var interactionId = beforeClose.interaction().getInteractionId(); var sequenceId = XrayUtil.sequenceId(interactionId); _XrayEvent.interactionClose("close interaction %s", interactionId); diff --git a/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java b/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java index 3694824855a..95f18fcb0f2 100644 --- a/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java +++ b/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java @@ -19,21 +19,22 @@ package org.apache.causeway.core.security._testing; import java.util.Optional; -import java.util.Stack; import java.util.UUID; import java.util.concurrent.Callable; import java.util.function.Function; +import org.jspecify.annotations.NonNull; + import org.apache.causeway.applib.services.command.Command; import org.apache.causeway.applib.services.iactn.Execution; import org.apache.causeway.applib.services.iactn.Interaction; import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionLayer; +import org.apache.causeway.applib.services.iactnlayer.InteractionLayerStack; import org.apache.causeway.applib.services.iactnlayer.InteractionService; import org.apache.causeway.applib.services.user.UserMemento; import org.apache.causeway.commons.functional.ThrowingRunnable; -import org.jspecify.annotations.NonNull; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -44,29 +45,28 @@ public class InteractionService_forTesting implements InteractionService { - private Stack interactionLayers = new Stack<>(); + final InteractionLayerStack layerStack = new InteractionLayerStack(); - @Override - public InteractionLayer openInteraction() { +// @Override + private InteractionLayer openInteraction() { final UserMemento userMemento = UserMemento.system(); return openInteraction(InteractionContext.ofUserWithSystemDefaults(userMemento)); } - @Override - public InteractionLayer openInteraction(final @NonNull InteractionContext interactionContext) { +// @Override + private InteractionLayer openInteraction(final @NonNull InteractionContext interactionContext) { final Interaction interaction = new Interaction_forTesting(); - return interactionLayers.push( - new InteractionLayer(interaction, interactionContext)); + return layerStack.push(interaction, interactionContext); } @Override public void closeInteractionLayers() { - interactionLayers.clear(); + layerStack.clear(); } @Override public boolean isInInteraction() { - return interactionLayers.size()>0; + return !layerStack.isEmpty(); } @Override public Optional getInteractionId() { @@ -76,13 +76,13 @@ public boolean isInInteraction() { } @Override public Optional currentInteractionLayer() { - return interactionLayers.isEmpty() + return layerStack.isEmpty() ? Optional.empty() - : Optional.of(interactionLayers.peek()); + : Optional.of(layerStack.peek()); } @Override public int getInteractionLayerCount() { - return interactionLayers.size(); + return layerStack.size(); } @Override @SneakyThrows @@ -91,7 +91,7 @@ public R call(final @NonNull InteractionContext interactionContext, final @N openInteraction(interactionContext); return callable.call(); } finally { - interactionLayers.pop(); + layerStack.pop(); } } @@ -101,7 +101,7 @@ public void run(final @NonNull InteractionContext interactionContext, final @Non openInteraction(interactionContext); runnable.run(); } finally { - interactionLayers.pop(); + layerStack.pop(); } } @@ -111,7 +111,7 @@ public void runAnonymous(final @NonNull ThrowingRunnable runnable) { openInteraction(); runnable.run(); } finally { - interactionLayers.pop(); + layerStack.pop(); } } @@ -121,7 +121,7 @@ public R callAnonymous(final @NonNull Callable callable) { openInteraction(); return callable.call(); } finally { - interactionLayers.pop(); + layerStack.pop(); } } @@ -136,6 +136,11 @@ static class Interaction_forTesting implements Interaction { @Override public Command getCommand() { return null; } @Override public Execution getCurrentExecution() { return null; } @Override public Execution getPriorExecution() { return null; } - }; + } + + @Override + public TestSupport testSupport(final T model) { + throw new UnsupportedOperationException(); + } } 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 cb09a0b956a..49124ebbc3a 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 @@ -38,6 +38,7 @@ import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.wrapper.WrapperFactory; import org.apache.causeway.applib.services.wrapper.WrapperFactory.AsyncProxy; import org.apache.causeway.applib.services.xactn.TransactionService; @@ -69,6 +70,7 @@ public abstract class BackgroundService_IntegTestAbstract extends CausewayIntegr JobExecutionContext mockQuartzJobExecutionContext = Mockito.mock(JobExecutionContext.class); Bookmark bookmark; + private TestSupport testSupport; protected abstract T newCounter(String name); @@ -87,6 +89,7 @@ static void reset_environment() { @BeforeEach void setup_counter() { + this.testSupport = interactionService.testSupport(); transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { counterRepository.removeAll(); @@ -211,27 +214,29 @@ void using_background_service() { // when (simulate quartz running in the background) runBackgroundCommandsJob.execute(mockQuartzJobExecutionContext); - interactionService.nextInteraction(); - - // then bumped - transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { - var counter = bookmarkService.lookup(bookmark, Counter.class).orElseThrow(); - assertThat(counter.getNum()).isEqualTo(1L); - }).ifFailureFail(); - - // and marked as started and completed - transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { - var after = commandLogEntryRepository.findAll(); - assertThat(after).hasSize(1); - CommandLogEntry commandLogEntryAfter = after.get(0); - - assertThat(commandLogEntryAfter) - .satisfies(x -> assertThat(x.getStartedAt()).isNotNull()) // changed - .satisfies(x -> assertThat(x.getCompletedAt()).isNotNull()) // changed - .satisfies(x -> assertThat(x.getResult()).isNotNull()) // changed - .satisfies(x -> assertThat(x.getResultSummary()).isNotNull()) // changed - ; - }).ifFailureFail(); + testSupport.nextInteraction(ia->{ + + // then bumped + transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { + var counter = bookmarkService.lookup(bookmark, Counter.class).orElseThrow(); + assertThat(counter.getNum()).isEqualTo(1L); + }).ifFailureFail(); + + // and marked as started and completed + transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { + var after = commandLogEntryRepository.findAll(); + assertThat(after).hasSize(1); + CommandLogEntry commandLogEntryAfter = after.get(0); + + assertThat(commandLogEntryAfter) + .satisfies(x -> assertThat(x.getStartedAt()).isNotNull()) // changed + .satisfies(x -> assertThat(x.getCompletedAt()).isNotNull()) // changed + .satisfies(x -> assertThat(x.getResult()).isNotNull()) // changed + .satisfies(x -> assertThat(x.getResultSummary()).isNotNull()) // changed + ; + }).ifFailureFail(); + + }); } diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java index 2a5d2951337..8d29e0e4052 100644 --- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java +++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java @@ -39,6 +39,7 @@ import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker; import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.sudo.SudoService; import org.apache.causeway.applib.services.user.UserMemento; import org.apache.causeway.applib.services.wrapper.WrapperFactory; @@ -58,6 +59,17 @@ public abstract class CommandLog_IntegTestAbstract extends CausewayIntegrationTestAbstract { + @Inject CommandLogEntryRepository commandLogEntryRepository; + @Inject SudoService sudoService; + @Inject ClockService clockService; + @Inject InteractionService interactionService; + @Inject InteractionLayerTracker interactionLayerTracker; + @Inject CounterRepository counterRepository; + @Inject WrapperFactory wrapperFactory; + @Inject BookmarkService bookmarkService; + @Inject CausewayBeanTypeRegistry causewayBeanTypeRegistry; + + @BeforeAll static void beforeAll() { CausewayPresets.forcePrototyping(); @@ -65,23 +77,25 @@ static void beforeAll() { Counter counter1; Counter counter2; + private TestSupport testSupport; @BeforeEach void beforeEach() { - interactionService.nextInteraction(); + this.testSupport = interactionService.testSupport(); + testSupport.nextInteraction(ia->{ + counterRepository.removeAll(); + commandLogEntryRepository.removeAll(); - counterRepository.removeAll(); - commandLogEntryRepository.removeAll(); + assertThat(counterRepository.find()).isEmpty(); - assertThat(counterRepository.find()).isEmpty(); + counter1 = counterRepository.persist(newCounter("counter-1")); + counter2 = counterRepository.persist(newCounter("counter-2")); - counter1 = counterRepository.persist(newCounter("counter-1")); - counter2 = counterRepository.persist(newCounter("counter-2")); + assertThat(counterRepository.find()).hasSize(2); - assertThat(counterRepository.find()).hasSize(2); - - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isEmpty(); + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isEmpty(); + }); } protected abstract T newCounter(String name); @@ -91,33 +105,34 @@ void invoke_mixin() { // when wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - interactionService.nextInteraction(); - - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isPresent(); - - CommandLogEntry commandLogEntry = mostRecentCompleted.get(); - - assertThat(commandLogEntry.getInteractionId()).isNotNull(); - assertThat(commandLogEntry.getCompletedAt()).isNotNull(); - assertThat(commandLogEntry.getDuration()).isNotNull(); - assertThat(commandLogEntry.getException()).isEqualTo(""); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingMixin"); - assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); - assertThat(commandLogEntry.getResult()).isNotNull(); - assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK"); - assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); - assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); - assertThat(commandLogEntry.getTarget()).isNotNull(); - assertThat(commandLogEntry.getTimestamp()).isNotNull(); - assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); - assertThat(commandLogEntry.getCommandDto()).isNotNull(); - CommandDto commandDto = commandLogEntry.getCommandDto(); - assertThat(commandDto).isNotNull(); - assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class); - assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); + testSupport.nextInteraction(ia->{ + + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isPresent(); + + CommandLogEntry commandLogEntry = mostRecentCompleted.get(); + + assertThat(commandLogEntry.getInteractionId()).isNotNull(); + assertThat(commandLogEntry.getCompletedAt()).isNotNull(); + assertThat(commandLogEntry.getDuration()).isNotNull(); + assertThat(commandLogEntry.getException()).isEqualTo(""); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingMixin"); + assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); + assertThat(commandLogEntry.getResult()).isNotNull(); + assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK"); + assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); + assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); + assertThat(commandLogEntry.getTarget()).isNotNull(); + assertThat(commandLogEntry.getTimestamp()).isNotNull(); + assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); + assertThat(commandLogEntry.getCommandDto()).isNotNull(); + CommandDto commandDto = commandLogEntry.getCommandDto(); + assertThat(commandDto).isNotNull(); + assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class); + assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); + }); } @Test @@ -125,57 +140,56 @@ void invoke_direct() { // when wrapperFactory.wrap(counter1).bumpUsingDeclaredAction(); - interactionService.nextInteraction(); - - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isPresent(); - - CommandLogEntry commandLogEntry = mostRecentCompleted.get(); - - assertThat(commandLogEntry.getInteractionId()).isNotNull(); - assertThat(commandLogEntry.getCompletedAt()).isNotNull(); - assertThat(commandLogEntry.getDuration()).isNotNull(); - assertThat(commandLogEntry.getException()).isEqualTo(""); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingDeclaredAction"); - assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); - assertThat(commandLogEntry.getResult()).isNotNull(); - assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK"); - assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); - assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); - assertThat(commandLogEntry.getTarget()).isNotNull(); - assertThat(commandLogEntry.getTimestamp()).isNotNull(); - assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); - assertThat(commandLogEntry.getCommandDto()).isNotNull(); - CommandDto commandDto = commandLogEntry.getCommandDto(); - assertThat(commandDto).isNotNull(); - assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class); - assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); + testSupport.nextInteraction(ia->{ + + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isPresent(); + + CommandLogEntry commandLogEntry = mostRecentCompleted.get(); + + assertThat(commandLogEntry.getInteractionId()).isNotNull(); + assertThat(commandLogEntry.getCompletedAt()).isNotNull(); + assertThat(commandLogEntry.getDuration()).isNotNull(); + assertThat(commandLogEntry.getException()).isEqualTo(""); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingDeclaredAction"); + assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); + assertThat(commandLogEntry.getResult()).isNotNull(); + assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK"); + assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); + assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); + assertThat(commandLogEntry.getTarget()).isNotNull(); + assertThat(commandLogEntry.getTimestamp()).isNotNull(); + assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); + assertThat(commandLogEntry.getCommandDto()).isNotNull(); + CommandDto commandDto = commandLogEntry.getCommandDto(); + assertThat(commandDto).isNotNull(); + assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class); + assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); + }); } @Test void invoke_mixin_disabled() { - // when wrapperFactory.wrapMixin(Counter_bumpUsingMixinWithCommandPublishingDisabled.class, counter1).act(); - interactionService.nextInteraction(); - - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isEmpty(); + testSupport.nextInteraction(ia->{ + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isEmpty(); + }); } @Test void invoke_direct_disabled() { - // when wrapperFactory.wrap(counter1).bumpUsingDeclaredActionWithCommandPublishingDisabled(); - interactionService.nextInteraction(); - - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isEmpty(); + testSupport.nextInteraction(ia->{ + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isEmpty(); + }); } @Test @@ -183,32 +197,33 @@ void edit() { // when wrapperFactory.wrap(counter1).setNum(99L); - interactionService.nextInteraction(); - - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isPresent(); - - CommandLogEntry commandLogEntry = mostRecentCompleted.get(); - - assertThat(commandLogEntry.getInteractionId()).isNotNull(); - assertThat(commandLogEntry.getCompletedAt()).isNotNull(); - assertThat(commandLogEntry.getDuration()).isNotNull(); - assertThat(commandLogEntry.getException()).isEqualTo(""); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#num"); - assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); - assertThat(commandLogEntry.getResult()).isNull(); - assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK (VOID)"); - assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); - assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); - assertThat(commandLogEntry.getTarget()).isNotNull(); - assertThat(commandLogEntry.getTimestamp()).isNotNull(); - assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); - CommandDto commandDto = commandLogEntry.getCommandDto(); - assertThat(commandDto).isNotNull(); - assertThat(commandDto.getMember()).isInstanceOf(PropertyDto.class); - assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); + testSupport.nextInteraction(ia->{ + + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isPresent(); + + CommandLogEntry commandLogEntry = mostRecentCompleted.get(); + + assertThat(commandLogEntry.getInteractionId()).isNotNull(); + assertThat(commandLogEntry.getCompletedAt()).isNotNull(); + assertThat(commandLogEntry.getDuration()).isNotNull(); + assertThat(commandLogEntry.getException()).isEqualTo(""); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#num"); + assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); + assertThat(commandLogEntry.getResult()).isNull(); + assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK (VOID)"); + assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); + assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); + assertThat(commandLogEntry.getTarget()).isNotNull(); + assertThat(commandLogEntry.getTimestamp()).isNotNull(); + assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); + CommandDto commandDto = commandLogEntry.getCommandDto(); + assertThat(commandDto).isNotNull(); + assertThat(commandDto.getMember()).isInstanceOf(PropertyDto.class); + assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); + }); } @Test @@ -216,262 +231,271 @@ void edit_disabled() { // when wrapperFactory.wrap(counter1).setNum2(99L); - interactionService.closeInteractionLayers(); // to flush + testSupport.nextInteraction(ia->{ - interactionService.openInteraction(); + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isEmpty(); - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isEmpty(); + }); } @Test void roundtrip_CLE_bookmarks() { + class Model { + CommandDto commandDto; + Bookmark cleBookmark; + } + var testSupport = interactionService.testSupport(new Model()); + // given wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - interactionService.nextInteraction(); + testSupport.nextInteraction(model->{ - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + CommandLogEntry commandLogEntry = commandLogEntryRepository.findMostRecentCompleted().get(); + model.commandDto = commandLogEntry.getCommandDto(); - CommandLogEntry commandLogEntry = mostRecentCompleted.get(); - CommandDto commandDto = commandLogEntry.getCommandDto(); + // when + Optional cleBookmarkIfAny = bookmarkService.bookmarkFor(commandLogEntry); - // when - Optional cleBookmarkIfAny = bookmarkService.bookmarkFor(commandLogEntry); - - // then - assertThat(cleBookmarkIfAny).isPresent(); - Bookmark cleBookmark = cleBookmarkIfAny.get(); - String identifier = cleBookmark.identifier(); - if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { - assertThat(identifier).startsWith("u_"); - UUID.fromString(identifier.substring("u_".length())); // should not fail, ie check the format is as we expect - } else { - UUID.fromString(identifier); // should not fail, ie check the format is as we expect - } + // then + assertThat(cleBookmarkIfAny).isPresent(); + model.cleBookmark = cleBookmarkIfAny.get(); + String identifier = model.cleBookmark.identifier(); + if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { + assertThat(identifier).startsWith("u_"); + UUID.fromString(identifier.substring("u_".length())); // should not fail, ie check the format is as we expect + } else { + UUID.fromString(identifier); // should not fail, ie check the format is as we expect + } + + }); // when we start a new session and lookup from the bookmark - interactionService.nextInteraction(); + testSupport.nextInteraction(model->{ - Optional cle2IfAny = bookmarkService.lookup(cleBookmarkIfAny.get()); - assertThat(cle2IfAny).isPresent(); + Optional cle2IfAny = bookmarkService.lookup(model.cleBookmark); + assertThat(cle2IfAny).isPresent(); - CommandLogEntry cle2 = (CommandLogEntry) cle2IfAny.get(); - CommandDto commandDto2 = cle2.getCommandDto(); + CommandLogEntry cle2 = (CommandLogEntry) cle2IfAny.get(); + CommandDto commandDto2 = cle2.getCommandDto(); - assertThat(commandDto2).isEqualTo(commandDto); + assertThat(commandDto2).isEqualTo(model.commandDto); + }); } @Test void test_all_the_repository_methods() { + class Model { + UUID commandTarget1User1Id; + UUID commandTarget1User2Id; + UUID commandTarget1User1YesterdayId; + } + var testSupport = interactionService.testSupport(new Model()); + // given sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); }); - interactionService.nextInteraction(); - // when - Optional commandTarget1User1IfAny = commandLogEntryRepository.findMostRecentCompleted(); - - // then - Assertions.assertThat(commandTarget1User1IfAny).isPresent(); - var commandTarget1User1 = commandTarget1User1IfAny.get(); - var commandTarget1User1Id = commandTarget1User1.getInteractionId(); - - // given (different user, same target, same day) - counter1 = counterRepository.findByName("counter-1"); - sudoService.run( - InteractionContext.switchUser( - UserMemento.builder("user-2").build()), - () -> wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act() - ); - interactionService.nextInteraction(); + testSupport.nextInteraction(model->{ + // when + Optional commandTarget1User1IfAny = commandLogEntryRepository.findMostRecentCompleted(); - // when - Optional commandTarget1User2IfAny = commandLogEntryRepository.findMostRecentCompleted(); - - // then - Assertions.assertThat(commandTarget1User2IfAny).isPresent(); - var commandTarget1User2 = commandTarget1User2IfAny.get(); - var commandTarget1User2Id = commandTarget1User2.getInteractionId(); - - // given (same user, same target, yesterday) - counter1 = counterRepository.findByName("counter-1"); - final UUID[] commandTarget1User1YesterdayIdHolder = new UUID[1]; - sudoService.run( - InteractionContext.switchUser( - UserMemento.builder("user-1").build()), - () -> { - var yesterday = clockService.getClock().nowAsLocalDateTime().minusDays(1); - sudoService.run( - InteractionContext.switchClock(VirtualClock.nowAt(yesterday)), - () -> { - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - commandTarget1User1YesterdayIdHolder[0] = interactionLayerTracker.currentInteraction().get().getInteractionId(); - interactionService.closeInteractionLayers(); // to flush within changed time... - } - ); - }); - interactionService.openInteraction(); - - // when, then - final UUID commandTarget1User1YesterdayId = commandTarget1User1YesterdayIdHolder[0]; - - // given (same user, different target, same day) - counter2 = counterRepository.findByName("counter-2"); - sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); + // then + Assertions.assertThat(commandTarget1User1IfAny).isPresent(); + var commandTarget1User1 = commandTarget1User1IfAny.get(); + model.commandTarget1User1Id = commandTarget1User1.getInteractionId(); + + // given (different user, same target, same day) + counter1 = counterRepository.findByName("counter-1"); + sudoService.run( + InteractionContext.switchUser( + UserMemento.builder("user-2").build()), + () -> wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act() + ); }); - interactionService.nextInteraction(); - - // when - Optional commandTarget2User1IfAny = commandLogEntryRepository.findMostRecentCompleted(); - // then - Assertions.assertThat(commandTarget2User1IfAny).isPresent(); - var commandTarget2User1 = commandTarget2User1IfAny.get(); - var commandTarget2User1Id = commandTarget2User1.getInteractionId(); + testSupport.nextInteraction(model->{ + // when + Optional commandTarget1User2IfAny = commandLogEntryRepository.findMostRecentCompleted(); - // when - Optional commandTarget1User1ById = commandLogEntryRepository.findByInteractionId(commandTarget1User1Id); - Optional commandTarget1User2ById = commandLogEntryRepository.findByInteractionId(commandTarget1User2Id); - Optional commandTarget1User1YesterdayById = commandLogEntryRepository.findByInteractionId(commandTarget1User1YesterdayId); - Optional commandTarget2User1ById = commandLogEntryRepository.findByInteractionId(commandTarget2User1Id); - - // then - Assertions.assertThat(commandTarget1User1ById).isPresent(); - Assertions.assertThat(commandTarget1User2ById).isPresent(); - Assertions.assertThat(commandTarget1User1YesterdayById).isPresent(); - Assertions.assertThat(commandTarget2User1ById).isPresent(); - Assertions.assertThat(commandTarget2User1ById.get()).isSameAs(commandTarget2User1); + // then + Assertions.assertThat(commandTarget1User2IfAny).isPresent(); + var commandTarget1User2 = commandTarget1User2IfAny.get(); + model.commandTarget1User2Id = commandTarget1User2.getInteractionId(); + + // given (same user, same target, yesterday) + counter1 = counterRepository.findByName("counter-1"); + sudoService.run( + InteractionContext.switchUser( + UserMemento.builder("user-1").build()), + () -> { + var yesterday = clockService.getClock().nowAsLocalDateTime().minusDays(1); + sudoService.run( + InteractionContext.switchClock(VirtualClock.nowAt(yesterday)), + () -> { + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); + // when, then + model.commandTarget1User1YesterdayId = interactionLayerTracker.currentInteraction().get().getInteractionId(); + interactionService.closeInteractionLayers(); // to flush within changed time... + } + ); + }); + testSupport.openInteraction(); + + // given (same user, different target, same day) + counter2 = counterRepository.findByName("counter-2"); + sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); + }); + }); - // given - commandTarget1User1 = commandTarget1User1ById.get(); - commandTarget1User2 = commandTarget1User2ById.get(); - @SuppressWarnings("unused") - var commandTarget1User1Yesterday = commandTarget1User1YesterdayById.get(); - commandTarget2User1 = commandTarget2User1ById.get(); + testSupport.nextInteraction(model->{ - var target1 = commandTarget1User1.getTarget(); - var username1 = commandTarget1User1.getUsername(); - var from = commandTarget1User1.getStartedAt().toLocalDateTime().toLocalDate(); - var to = from.plusDays(1); + var commandTarget1User1Id = model.commandTarget1User1Id; + var commandTarget1User2Id = model.commandTarget1User2Id; + var commandTarget1User1YesterdayId = model.commandTarget1User1YesterdayId; - // when - List notYetReplayed = commandLogEntryRepository.findNotYetReplayed(); + // when + Optional commandTarget2User1IfAny = commandLogEntryRepository.findMostRecentCompleted(); - // then - Assertions.assertThat(notYetReplayed).isEmpty(); + // then + Assertions.assertThat(commandTarget2User1IfAny).isPresent(); + var commandTarget2User1 = commandTarget2User1IfAny.get(); + var commandTarget2User1Id = commandTarget2User1.getInteractionId(); - if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { + // when + Optional commandTarget1User1ById = commandLogEntryRepository.findByInteractionId(commandTarget1User1Id); + Optional commandTarget1User2ById = commandLogEntryRepository.findByInteractionId(commandTarget1User2Id); + Optional commandTarget1User1YesterdayById = commandLogEntryRepository.findByInteractionId(commandTarget1User1YesterdayId); + Optional commandTarget2User1ById = commandLogEntryRepository.findByInteractionId(commandTarget2User1Id); - // fails in JPA; possibly need to get the agent working for dirty tracking. + // then + Assertions.assertThat(commandTarget1User1ById).isPresent(); + Assertions.assertThat(commandTarget1User2ById).isPresent(); + Assertions.assertThat(commandTarget1User1YesterdayById).isPresent(); + Assertions.assertThat(commandTarget2User1ById).isPresent(); + Assertions.assertThat(commandTarget2User1ById.get()).isSameAs(commandTarget2User1); // given - commandTarget1User1.setReplayState(ReplayState.PENDING); + var commandTarget1User1 = commandTarget1User1ById.get(); + var commandTarget1User2 = commandTarget1User2ById.get(); + @SuppressWarnings("unused") + var commandTarget1User1Yesterday = commandTarget1User1YesterdayById.get(); + commandTarget2User1 = commandTarget2User1ById.get(); + + var target1 = commandTarget1User1.getTarget(); + var username1 = commandTarget1User1.getUsername(); + var from = commandTarget1User1.getStartedAt().toLocalDateTime().toLocalDate(); + var to = from.plusDays(1); // when - List notYetReplayed2 = commandLogEntryRepository.findNotYetReplayed(); + List notYetReplayed = commandLogEntryRepository.findNotYetReplayed(); // then - Assertions.assertThat(notYetReplayed2).hasSize(1); - Assertions.assertThat(notYetReplayed2.get(0).getInteractionId()).isEqualTo(commandTarget1User1.getInteractionId()); - } + Assertions.assertThat(notYetReplayed).isEmpty(); - // when - List byFromAndTo = commandLogEntryRepository.findByFromAndTo(from, to); + if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { - // then - Assertions.assertThat(byFromAndTo).hasSize(3); - Assertions.assertThat(byFromAndTo.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent + // fails in JPA; possibly need to get the agent working for dirty tracking. - // when - List byTarget1AndFromAndTo = commandLogEntryRepository.findByTargetAndFromAndTo(target1, from, to); + // given + commandTarget1User1.setReplayState(ReplayState.PENDING); - // then - Assertions.assertThat(byTarget1AndFromAndTo).hasSize(2); - Assertions.assertThat(byTarget1AndFromAndTo.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // the more recent + // when + List notYetReplayed2 = commandLogEntryRepository.findNotYetReplayed(); - // when - List recentByTargetOfCommand1 = commandLogEntryRepository.findRecentByTarget(target1); + // then + Assertions.assertThat(notYetReplayed2).hasSize(1); + Assertions.assertThat(notYetReplayed2.get(0).getInteractionId()).isEqualTo(commandTarget1User1.getInteractionId()); + } - // then - Assertions.assertThat(recentByTargetOfCommand1).hasSize(3); - Assertions.assertThat(recentByTargetOfCommand1.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // the more recent + // when + List byFromAndTo = commandLogEntryRepository.findByFromAndTo(from, to); - // when - List recentByUsername = commandLogEntryRepository.findRecentByUsername(username1); + // then + Assertions.assertThat(byFromAndTo).hasSize(3); + Assertions.assertThat(byFromAndTo.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent - // then - Assertions.assertThat(recentByUsername).hasSize(3); - Assertions.assertThat(recentByUsername.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent + // when + List byTarget1AndFromAndTo = commandLogEntryRepository.findByTargetAndFromAndTo(target1, from, to); - // when - List byParent = commandLogEntryRepository.findByParent(commandTarget1User1); + // then + Assertions.assertThat(byTarget1AndFromAndTo).hasSize(2); + Assertions.assertThat(byTarget1AndFromAndTo.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // the more recent - // then // TODO: would need nested executions for this to show up. - Assertions.assertThat(byParent).isEmpty(); + // when + List recentByTargetOfCommand1 = commandLogEntryRepository.findRecentByTarget(target1); - // when - List completed = commandLogEntryRepository.findCompleted(); + // then + Assertions.assertThat(recentByTargetOfCommand1).hasSize(3); + Assertions.assertThat(recentByTargetOfCommand1.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // the more recent - // then - Assertions.assertThat(completed).hasSize(4); - Assertions.assertThat(completed.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent + // when + List recentByUsername = commandLogEntryRepository.findRecentByUsername(username1); - // when - List current = commandLogEntryRepository.findCurrent(); + // then + Assertions.assertThat(recentByUsername).hasSize(3); + Assertions.assertThat(recentByUsername.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent - // then // TODO: would need more sophistication in fixtures to test - Assertions.assertThat(current).isEmpty(); + // when + List byParent = commandLogEntryRepository.findByParent(commandTarget1User1); - // when - List since = commandLogEntryRepository.findSince(commandTarget1User1.getInteractionId(), 3); + // then // TODO: would need nested executions for this to show up. + Assertions.assertThat(byParent).isEmpty(); - // then - Assertions.assertThat(since).hasSize(2); - Assertions.assertThat(since.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // oldest first + // when + List completed = commandLogEntryRepository.findCompleted(); - // when - List sinceWithBatchSize1 = commandLogEntryRepository.findSince(commandTarget1User1.getInteractionId(), 1); + // then + Assertions.assertThat(completed).hasSize(4); + Assertions.assertThat(completed.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent - // then - Assertions.assertThat(sinceWithBatchSize1).hasSize(1); - Assertions.assertThat(sinceWithBatchSize1.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // oldest fist + // when + List current = commandLogEntryRepository.findCurrent(); - // when - Optional mostRecentReplayedIfAny = commandLogEntryRepository.findMostRecentReplayed(); + // then // TODO: would need more sophistication in fixtures to test + Assertions.assertThat(current).isEmpty(); - // then - Assertions.assertThat(mostRecentReplayedIfAny).isEmpty(); + // when + List since = commandLogEntryRepository.findSince(commandTarget1User1.getInteractionId(), 3); - if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { + // then + Assertions.assertThat(since).hasSize(2); + Assertions.assertThat(since.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // oldest first - // fails in JPA; possibly need to get the agent working for dirty tracking. + // when + List sinceWithBatchSize1 = commandLogEntryRepository.findSince(commandTarget1User1.getInteractionId(), 1); - // given - commandTarget1User1.setReplayState(ReplayState.OK); + // then + Assertions.assertThat(sinceWithBatchSize1).hasSize(1); + Assertions.assertThat(sinceWithBatchSize1.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // oldest fist // when - Optional mostRecentReplayedIfAny2 = commandLogEntryRepository.findMostRecentReplayed(); + Optional mostRecentReplayedIfAny = commandLogEntryRepository.findMostRecentReplayed(); // then - Assertions.assertThat(mostRecentReplayedIfAny2).isPresent(); - Assertions.assertThat(mostRecentReplayedIfAny2.get().getInteractionId()).isEqualTo(commandTarget1User1Id); - } - } + Assertions.assertThat(mostRecentReplayedIfAny).isEmpty(); - @Inject CommandLogEntryRepository commandLogEntryRepository; - @Inject SudoService sudoService; - @Inject ClockService clockService; - @Inject InteractionService interactionService; - @Inject InteractionLayerTracker interactionLayerTracker; - @Inject CounterRepository counterRepository; - @Inject WrapperFactory wrapperFactory; - @Inject BookmarkService bookmarkService; - @Inject CausewayBeanTypeRegistry causewayBeanTypeRegistry; + if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { + + // fails in JPA; possibly need to get the agent working for dirty tracking. + + // given + commandTarget1User1.setReplayState(ReplayState.OK); + + // when + Optional mostRecentReplayedIfAny2 = commandLogEntryRepository.findMostRecentReplayed(); + + // then + Assertions.assertThat(mostRecentReplayedIfAny2).isPresent(); + Assertions.assertThat(mostRecentReplayedIfAny2.get().getInteractionId()).isEqualTo(commandTarget1User1Id); + } + }); + } } diff --git a/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java b/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java index f235a176dfc..5558698bd76 100644 --- a/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java +++ b/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java @@ -39,6 +39,7 @@ import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker; import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.sudo.SudoService; import org.apache.causeway.applib.services.user.UserMemento; import org.apache.causeway.applib.services.wrapper.WrapperFactory; @@ -56,6 +57,15 @@ public abstract class ExecutionLog_IntegTestAbstract extends CausewayIntegrationTestAbstract { + @Inject ExecutionLogEntryRepository executionLogEntryRepository; + @Inject SudoService sudoService; + @Inject ClockService clockService; + @Inject InteractionService interactionService; + @Inject InteractionLayerTracker interactionLayerTracker; + @Inject CounterRepository counterRepository; + @Inject WrapperFactory wrapperFactory; + @Inject BookmarkService bookmarkService; + @BeforeAll static void beforeAll() { CausewayPresets.forcePrototyping(); @@ -63,10 +73,11 @@ static void beforeAll() { Counter counter1; Counter counter2; + private TestSupport testSupport; @BeforeEach void beforeEach() { - + this.testSupport = interactionService.testSupport(); counterRepository.removeAll(); executionLogEntryRepository.removeAll(); @@ -223,21 +234,29 @@ void roundtrip_ELE_bookmarks() { Integer.parseInt(identifier.substring(identifier.indexOf("_")+1)); // should not fail, ie check the format is as we expect // when we start a new session and lookup from the bookmark - interactionService.nextInteraction(); + testSupport.nextInteraction(model->{ - Optional cle2IfAny = bookmarkService.lookup(eleBookmarkIfAny.get()); - assertThat(cle2IfAny).isPresent(); + Optional cle2IfAny = bookmarkService.lookup(eleBookmarkIfAny.get()); + assertThat(cle2IfAny).isPresent(); - ExecutionLogEntry ele2 = (ExecutionLogEntry) cle2IfAny.get(); - InteractionDto interactionDto2 = ele2.getInteractionDto(); + ExecutionLogEntry ele2 = (ExecutionLogEntry) cle2IfAny.get(); + InteractionDto interactionDto2 = ele2.getInteractionDto(); - assertThat(interactionDto2).isEqualTo(interactionDto); + assertThat(interactionDto2).isEqualTo(interactionDto); + }); } @Test void test_all_the_repository_methods() { + class Model { + UUID executionTarget1User1Id; + UUID executionTarget1User2Id; + UUID executionTarget1User1YesterdayId; + } + var testSupport = interactionService.testSupport(new Model()); + // given sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); @@ -248,8 +267,10 @@ void test_all_the_repository_methods() { // then assertThat(executionsForTarget1User1).hasSize(1); - var executionTarget1User1 = executionsForTarget1User1.get(0); - var executionTarget1User1Id = executionTarget1User1.getInteractionId(); + { + var executionTarget1User1 = executionsForTarget1User1.get(0); + testSupport.model().executionTarget1User1Id = executionTarget1User1.getInteractionId(); + } // given (different user, same target, same day) counter1 = counterRepository.findByName("counter-1"); @@ -258,136 +279,134 @@ void test_all_the_repository_methods() { UserMemento.builder("user-2").build()), () -> wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act() ); - interactionService.nextInteraction(); - - // when - List executionsForTarget1User2 = executionLogEntryRepository.findMostRecent(1); - // then - assertThat(executionsForTarget1User2).hasSize(1); - var executionTarget1User2 = executionsForTarget1User2.get(0); - var executionTarget1User2Id = executionTarget1User2.getInteractionId(); + testSupport.nextInteraction(model->{ + + // when + List executionsForTarget1User2 = executionLogEntryRepository.findMostRecent(1); + + // then + assertThat(executionsForTarget1User2).hasSize(1); + var executionTarget1User2 = executionsForTarget1User2.get(0); + model.executionTarget1User2Id = executionTarget1User2.getInteractionId(); + + // given (same user, same target, yesterday) + counter1 = counterRepository.findByName("counter-1"); + + sudoService.run( + InteractionContext.switchUser( + UserMemento.builder("user-1").build()), + () -> { + var yesterday = clockService.getClock().nowAsLocalDateTime().minusDays(1); + sudoService.run( + InteractionContext.switchClock(VirtualClock.nowAt(yesterday)), + () -> { + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); + // when, then + model.executionTarget1User1YesterdayId = interactionLayerTracker.currentInteraction().get().getInteractionId(); + interactionService.closeInteractionLayers(); // to flush within changed time... + } + ); + }); + }); - // given (same user, same target, yesterday) - counter1 = counterRepository.findByName("counter-1"); - final UUID[] executionTarget1User1YesterdayIdHolder = new UUID[1]; - sudoService.run( - InteractionContext.switchUser( - UserMemento.builder("user-1").build()), - () -> { - var yesterday = clockService.getClock().nowAsLocalDateTime().minusDays(1); - sudoService.run( - InteractionContext.switchClock(VirtualClock.nowAt(yesterday)), - () -> { - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - executionTarget1User1YesterdayIdHolder[0] = interactionLayerTracker.currentInteraction().get().getInteractionId(); - interactionService.closeInteractionLayers(); // to flush within changed time... - } - ); - }); - interactionService.openInteraction(); - - // when, then - final UUID executionTarget1User1YesterdayId = executionTarget1User1YesterdayIdHolder[0]; + testSupport.openInteraction(); // given (same user, different target, same day) counter2 = counterRepository.findByName("counter-2"); sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); }); - interactionService.nextInteraction(); - // when - List executionTarget2User1IfAny = executionLogEntryRepository.findMostRecent(1); + testSupport.nextInteraction(model->{ - // then - assertThat(executionTarget2User1IfAny).hasSize(1); - var executionTarget2User1 = executionTarget2User1IfAny.get(0); - var executionTarget2User1Id = executionTarget2User1.getInteractionId(); + var executionTarget1User1Id = model.executionTarget1User1Id; + var executionTarget1User2Id = model.executionTarget1User2Id; + var executionTarget1User1YesterdayId = model.executionTarget1User1YesterdayId; - // when - Optional executionTarget1User1ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User1Id, 0); - Optional executionTarget1User2ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User2Id, 0); - Optional executionTarget1User1YesterdayById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User1YesterdayId, 0); - Optional executionTarget2User1ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget2User1Id, 0); + // when + List executionTarget2User1IfAny = executionLogEntryRepository.findMostRecent(1); - // then - assertThat(executionTarget1User1ById).isPresent(); - assertThat(executionTarget1User2ById).isPresent(); - assertThat(executionTarget1User1YesterdayById).isPresent(); - assertThat(executionTarget2User1ById).isPresent(); - assertThat(executionTarget2User1ById.get()).isSameAs(executionTarget2User1); + // then + assertThat(executionTarget2User1IfAny).hasSize(1); + var executionTarget2User1 = executionTarget2User1IfAny.get(0); + var executionTarget2User1Id = executionTarget2User1.getInteractionId(); - // given - counter1 = counterRepository.findByName("counter-1"); - executionTarget1User1 = executionTarget1User1ById.get(); - executionTarget1User2 = executionTarget1User2ById.get(); - var executionTarget1User1Yesterday = executionTarget1User1YesterdayById.get(); - executionTarget2User1 = executionTarget2User1ById.get(); + // when + Optional executionTarget1User1ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User1Id, 0); + Optional executionTarget1User2ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User2Id, 0); + Optional executionTarget1User1YesterdayById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User1YesterdayId, 0); + Optional executionTarget2User1ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget2User1Id, 0); - var target1 = executionTarget1User1.getTarget(); - var username1 = executionTarget1User1.getUsername(); - Timestamp from1 = executionTarget1User1.getStartedAt(); - Timestamp to1 = Timestamp.valueOf(from1.toLocalDateTime().plusDays(1)); - var bookmark1 = bookmarkService.bookmarkForElseFail(counter1); + // then + assertThat(executionTarget1User1ById).isPresent(); + assertThat(executionTarget1User2ById).isPresent(); + assertThat(executionTarget1User1YesterdayById).isPresent(); + assertThat(executionTarget2User1ById).isPresent(); + assertThat(executionTarget2User1ById.get()).isSameAs(executionTarget2User1); - // when - List recentByTarget = executionLogEntryRepository.findRecentByTarget(bookmark1); + // given + counter1 = counterRepository.findByName("counter-1"); + var executionTarget1User1 = executionTarget1User1ById.get(); + var executionTarget1User2 = executionTarget1User2ById.get(); + var executionTarget1User1Yesterday = executionTarget1User1YesterdayById.get(); + executionTarget2User1 = executionTarget2User1ById.get(); - // then - assertThat(recentByTarget).hasSize(3); + var target1 = executionTarget1User1.getTarget(); + var username1 = executionTarget1User1.getUsername(); + Timestamp from1 = executionTarget1User1.getStartedAt(); + Timestamp to1 = Timestamp.valueOf(from1.toLocalDateTime().plusDays(1)); + var bookmark1 = bookmarkService.bookmarkForElseFail(counter1); - // when - List byTargetAndTimestampBefore = executionLogEntryRepository.findByTargetAndTimestampBefore(bookmark1, from1); + // when + List recentByTarget = executionLogEntryRepository.findRecentByTarget(bookmark1); - // then - assertThat(byTargetAndTimestampBefore).hasSize(2); // yesterday, plus cmd1 + // then + assertThat(recentByTarget).hasSize(3); - // when - List byTargetAndTimestampAfter = executionLogEntryRepository.findByTargetAndTimestampAfter(bookmark1, from1); + // when + List byTargetAndTimestampBefore = executionLogEntryRepository.findByTargetAndTimestampBefore(bookmark1, from1); - // then - assertThat(byTargetAndTimestampAfter).hasSize(2); // cmd1, 2nd + // then + assertThat(byTargetAndTimestampBefore).hasSize(2); // yesterday, plus cmd1 - // when - List byTargetAndTimestampBetween = executionLogEntryRepository.findByTargetAndTimestampBetween(bookmark1, from1, to1); + // when + List byTargetAndTimestampAfter = executionLogEntryRepository.findByTargetAndTimestampAfter(bookmark1, from1); - // then - assertThat(byTargetAndTimestampBetween).hasSize(2); // 1st and 2nd for this target + // then + assertThat(byTargetAndTimestampAfter).hasSize(2); // cmd1, 2nd - // when - List byTimestampBefore = executionLogEntryRepository.findByTimestampBefore(from1); + // when + List byTargetAndTimestampBetween = executionLogEntryRepository.findByTargetAndTimestampBetween(bookmark1, from1, to1); - // then - assertThat(byTimestampBefore).hasSize(2); // cmd1 plus yesterday + // then + assertThat(byTargetAndTimestampBetween).hasSize(2); // 1st and 2nd for this target - // when - List byTimestampAfter = executionLogEntryRepository.findByTimestampAfter(from1); + // when + List byTimestampBefore = executionLogEntryRepository.findByTimestampBefore(from1); - // then - assertThat(byTimestampAfter).hasSize(3); // cmd1, 2nd, and for other target + // then + assertThat(byTimestampBefore).hasSize(2); // cmd1 plus yesterday - // when - List byTimestampBetween = executionLogEntryRepository.findByTimestampBetween(from1, to1); + // when + List byTimestampAfter = executionLogEntryRepository.findByTimestampAfter(from1); - // then - assertThat(byTimestampBetween).hasSize(3); // 1st and 2nd for this target, and other target + // then + assertThat(byTimestampAfter).hasSize(3); // cmd1, 2nd, and for other target - // when - List byUsername = executionLogEntryRepository.findRecentByUsername(username1); + // when + List byTimestampBetween = executionLogEntryRepository.findByTimestampBetween(from1, to1); - // then - assertThat(byUsername).hasSize(3); + // then + assertThat(byTimestampBetween).hasSize(3); // 1st and 2nd for this target, and other target - } + // when + List byUsername = executionLogEntryRepository.findRecentByUsername(username1); - @Inject ExecutionLogEntryRepository executionLogEntryRepository; - @Inject SudoService sudoService; - @Inject ClockService clockService; - @Inject InteractionService interactionService; - @Inject InteractionLayerTracker interactionLayerTracker; - @Inject CounterRepository counterRepository; - @Inject WrapperFactory wrapperFactory; - @Inject BookmarkService bookmarkService; + // then + assertThat(byUsername).hasSize(3); + }); + + } } diff --git a/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java b/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java index c35fe6211d9..c98204dd148 100644 --- a/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java +++ b/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java @@ -36,6 +36,7 @@ import org.apache.causeway.applib.services.clock.ClockService; import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.sudo.SudoService; import org.apache.causeway.applib.services.user.UserMemento; import org.apache.causeway.applib.services.wrapper.WrapperFactory; @@ -60,9 +61,11 @@ static void beforeAll() { Counter counter1; Counter counter2; + private TestSupport testSupport; @BeforeEach void beforeEach() { + this.testSupport = interactionService.testSupport(); counterRepository.removeAll(); executionOutboxEntryRepository.removeAll(); @@ -86,7 +89,7 @@ void invoke_mixin() { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); interactionService.closeInteractionLayers(); // to flush - interactionService.openInteraction(); + testSupport.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -117,7 +120,7 @@ void invoke_direct() { wrapperFactory.wrap(counter1).bumpUsingDeclaredAction(); interactionService.closeInteractionLayers(); // to flush - interactionService.openInteraction(); + testSupport.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -148,7 +151,7 @@ void invoke_mixin_disabled() { wrapperFactory.wrapMixin(Counter_bumpUsingMixinWithExecutionPublishingDisabled.class, counter1).act(); interactionService.closeInteractionLayers(); // to flush - interactionService.openInteraction(); + testSupport.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -162,7 +165,7 @@ void invoke_direct_disabled() { wrapperFactory.wrap(counter1).bumpUsingDeclaredActionWithExecutionPublishingDisabled(); interactionService.closeInteractionLayers(); // to flush - interactionService.openInteraction(); + testSupport.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -176,7 +179,7 @@ void edit() { wrapperFactory.wrap(counter1).setNum(99L); interactionService.closeInteractionLayers(); // to flush - interactionService.openInteraction(); + testSupport.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -206,7 +209,7 @@ void edit_disabled() { wrapperFactory.wrap(counter1).setNum2(99L); interactionService.closeInteractionLayers(); // to flush - interactionService.openInteraction(); + testSupport.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -220,7 +223,7 @@ void roundtrip_EOE_bookmarks() { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); interactionService.closeInteractionLayers(); // to flush - interactionService.openInteraction(); + testSupport.openInteraction(); List all = executionOutboxEntryRepository.findOldest(); ExecutionOutboxEntry executionLogEntry = all.get(0); @@ -238,7 +241,7 @@ void roundtrip_EOE_bookmarks() { // when we start a new session and lookup from the bookmark interactionService.closeInteractionLayers(); - interactionService.openInteraction(); + testSupport.openInteraction(); Optional cle2IfAny = bookmarkService.lookup(eleBookmarkIfAny.get()); assertThat(cle2IfAny).isPresent(); @@ -257,7 +260,7 @@ void test_all_the_repository_methods() { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); }); interactionService.closeInteractionLayers(); // to flush - interactionService.openInteraction(); + testSupport.openInteraction(); // when List executionTarget1User1IfAny = executionOutboxEntryRepository.findOldest(); diff --git a/extensions/security/audittrail/applib/src/test/java/org/apache/causeway/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java b/extensions/security/audittrail/applib/src/test/java/org/apache/causeway/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java index d182ff274b2..54042cafebc 100644 --- a/extensions/security/audittrail/applib/src/test/java/org/apache/causeway/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java +++ b/extensions/security/audittrail/applib/src/test/java/org/apache/causeway/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java @@ -31,6 +31,7 @@ import org.apache.causeway.applib.mixins.system.DomainChangeRecord; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.wrapper.WrapperFactory; import org.apache.causeway.core.config.presets.CausewayPresets; import org.apache.causeway.extensions.audittrail.applib.dom.AuditTrailEntry; @@ -42,6 +43,8 @@ public abstract class AuditTrail_IntegTestAbstract extends CausewayIntegrationTestAbstract { + private TestSupport testSupport; + @BeforeAll static void beforeAll() { CausewayPresets.forcePrototyping(); @@ -49,14 +52,15 @@ static void beforeAll() { @BeforeEach void setUp() { + this.testSupport = interactionService.testSupport(); counterRepository.removeAll(); - interactionService.nextInteraction(); - - auditTrailEntryRepository.removeAll(); - interactionService.nextInteraction(); - - assertThat(counterRepository.find()).isEmpty(); - assertThat(auditTrailEntryRepository.findAll()).isEmpty(); + testSupport.nextInteraction(model->{ + auditTrailEntryRepository.removeAll(); + }); + testSupport.nextInteraction(model->{ + assertThat(counterRepository.find()).isEmpty(); + assertThat(auditTrailEntryRepository.findAll()).isEmpty(); + }); } protected abstract Counter newCounter(String name); @@ -67,32 +71,33 @@ void created() { // when var counter1 = counterRepository.persist(newCounter("counter-1")); var target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); - interactionService.nextInteraction(); - - // then - var entries = auditTrailEntryRepository.findAll(); - var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).contains("name", "num", "num2"); - - var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("name")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#name")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("counter-1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isNull()); - assertThat(entriesById.get("num2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isNull()); + testSupport.nextInteraction(model->{ + + // then + var entries = auditTrailEntryRepository.findAll(); + var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).contains("name", "num", "num2"); + + var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("name")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#name")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("counter-1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isNull()); + assertThat(entriesById.get("num2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isNull()); + }); } @Test @@ -101,55 +106,62 @@ void updated_using_mixin() { // given var counter1 = counterRepository.persist(newCounter("counter-1")); var target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); - interactionService.nextInteraction(); - - auditTrailEntryRepository.removeAll(); - interactionService.nextInteraction(); - - assertThat(counterRepository.find()).hasSize(1); - assertThat(auditTrailEntryRepository.findAll()).isEmpty(); - - // when - counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - interactionService.nextInteraction(); - - // then - var entries = auditTrailEntryRepository.findAll(); - var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).containsExactly("num"); - - var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); - - // given - auditTrailEntryRepository.removeAll(); - interactionService.nextInteraction(); - - // when bump again - counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - interactionService.nextInteraction(); - - // then - entries = auditTrailEntryRepository.findAll(); - propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).containsExactly("num"); - - entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("2")); - + testSupport.nextInteraction(ia->{ + auditTrailEntryRepository.removeAll(); + }); + + testSupport.nextInteraction(ia->{ + assertThat(counterRepository.find()).hasSize(1); + assertThat(auditTrailEntryRepository.findAll()).isEmpty(); + + // when + var counter2 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); + }); + + testSupport.nextInteraction(ia->{ + + // then + var entries = auditTrailEntryRepository.findAll(); + var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).containsExactly("num"); + + var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); + + // given + auditTrailEntryRepository.removeAll(); + + }); + + testSupport.nextInteraction(ia->{ + + // when bump again + var counter2 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); + }); + + testSupport.nextInteraction(ia->{ + + // then + var entries = auditTrailEntryRepository.findAll(); + var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).containsExactly("num"); + + var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("2")); + }); } @Test @@ -160,40 +172,44 @@ void deleted() { counter1.setNum(1L); counter1.setNum2(2L); var target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); - interactionService.nextInteraction(); - - auditTrailEntryRepository.removeAll(); - interactionService.nextInteraction(); - // when - counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - counterRepository.remove(counter1); - interactionService.nextInteraction(); - - // then - var entries = auditTrailEntryRepository.findAll(); - var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).contains("name", "num", "num2"); - - var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("name")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#name")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("counter-1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")); - assertThat(entriesById.get("num2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")); + testSupport.nextInteraction(ia->{ + auditTrailEntryRepository.removeAll(); + }); + + testSupport.nextInteraction(ia->{ + // when + var counter2 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); + counterRepository.remove(counter2); + }); + + testSupport.nextInteraction(ia->{ + + // then + var entries = auditTrailEntryRepository.findAll(); + var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).contains("name", "num", "num2"); + + var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("name")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#name")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("counter-1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")); + assertThat(entriesById.get("num2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")); + }); } diff --git a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java index d4af4aaf560..b535c6edda3 100644 --- a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java +++ b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java @@ -78,7 +78,7 @@ CausewayModuleViewerWicketViewer.class, }) public class Configuration_usingWicket { - + @Bean public WicketTesterFactory wicketTesterFactory(final MetaModelContext mmc) { return new WicketTesterFactory(mmc); @@ -282,25 +282,22 @@ private static class PageFactory_forTesting implements IPageFactory { @Override public C newPage(final Class pageClass, final PageParameters parameters) { - if(DomainObjectPage.class.equals(pageClass)) { + if(DomainObjectPage.class.equals(pageClass)) return _Casts.uncheckedCast(DomainObjectPage.forPageParameters(parameters)); - } return delegate.newPage(pageClass, parameters); } @Override public C newPage(final Class pageClass) { - if(DomainObjectPage.class.equals(pageClass)) { + if(DomainObjectPage.class.equals(pageClass)) throw _Exceptions.illegalArgument("cannot instantiate DomainObjectPage without PageParameters"); - } return delegate.newPage(pageClass); } @Override public boolean isBookmarkable(final Class pageClass) { - if(DomainObjectPage.class.equals(pageClass)) { + if(DomainObjectPage.class.equals(pageClass)) return true; - } return delegate.isBookmarkable(pageClass); } } @@ -353,7 +350,7 @@ protected IPageFactory newPageFactory() { protected void internalInit() { super.internalInit(); // intercept AJAX requests and reload view-models so any detached entities are re-fetched - CausewayWicketAjaxRequestListenerUtil.setRootRequestMapper(this, metaModelContext); + CausewayWicketAjaxRequestListenerUtil.setRootRequestMapper(this); } } diff --git a/regressiontests/cmdexecauditsess/generic/src/main/java/org/apache/causeway/regressiontests/cmdexecauditsess/generic/integtest/CmdExecAuditSessLog_IntegTestAbstract.java b/regressiontests/cmdexecauditsess/generic/src/main/java/org/apache/causeway/regressiontests/cmdexecauditsess/generic/integtest/CmdExecAuditSessLog_IntegTestAbstract.java index 3b24b9bc08f..14ca623834e 100644 --- a/regressiontests/cmdexecauditsess/generic/src/main/java/org/apache/causeway/regressiontests/cmdexecauditsess/generic/integtest/CmdExecAuditSessLog_IntegTestAbstract.java +++ b/regressiontests/cmdexecauditsess/generic/src/main/java/org/apache/causeway/regressiontests/cmdexecauditsess/generic/integtest/CmdExecAuditSessLog_IntegTestAbstract.java @@ -33,6 +33,7 @@ import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.wrapper.WrapperFactory; import org.apache.causeway.core.config.beans.CausewayBeanTypeRegistry; import org.apache.causeway.core.config.presets.CausewayPresets; @@ -59,32 +60,35 @@ static void beforeAll() { } Bookmark target1; + private TestSupport testSupport; @BeforeEach void beforeEach() { - interactionService.nextInteraction(); - - counterRepository.removeAll(); - - assertThat(counterRepository.find()).isEmpty(); - - var counter1 = counterRepository.persist(newCounter("counter-1")); - target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); - - assertThat(counterRepository.find()).hasSize(1); - - interactionService.nextInteraction(); - commandLogEntryRepository.removeAll(); - executionLogEntryRepository.removeAll(); - executionOutboxEntryRepository.removeAll(); - auditTrailEntryRepository.removeAll(); - - interactionService.nextInteraction(); - - assertThat(commandLogEntryRepository.findAll()).isEmpty(); - assertThat(executionLogEntryRepository.findAll()).isEmpty(); - assertThat(executionOutboxEntryRepository.findAll()).isEmpty(); - assertThat(auditTrailEntryRepository.findAll()).isEmpty(); + this.testSupport = interactionService.testSupport(); + testSupport.nextInteraction(ia->{ + counterRepository.removeAll(); + + assertThat(counterRepository.find()).isEmpty(); + + var counter1 = counterRepository.persist(newCounter("counter-1")); + target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); + + assertThat(counterRepository.find()).hasSize(1); + }); + + testSupport.nextInteraction(ia->{ + commandLogEntryRepository.removeAll(); + executionLogEntryRepository.removeAll(); + executionOutboxEntryRepository.removeAll(); + auditTrailEntryRepository.removeAll(); + }); + + testSupport.nextInteraction(ia->{ + assertThat(commandLogEntryRepository.findAll()).isEmpty(); + assertThat(executionLogEntryRepository.findAll()).isEmpty(); + assertThat(executionOutboxEntryRepository.findAll()).isEmpty(); + assertThat(auditTrailEntryRepository.findAll()).isEmpty(); + }); } protected abstract Counter newCounter(String name); @@ -93,7 +97,7 @@ void beforeEach() { protected void assertEntityPublishingDisabledFor(final Class entityClass) { var objectSpecification = specificationLoader.loadSpecification(entityClass); - EntityChangePublishingFacet facet = objectSpecification.getFacet(EntityChangePublishingFacet.class); + EntityChangePublishingFacet facet = objectSpecification.lookupFacet(EntityChangePublishingFacet.class).orElse(null); Assertions.assertThat(facet) .satisfies(f -> assertThat(f).isNotNull()) .satisfies(f -> assertThat(f.isEnabled()).isFalse()) @@ -107,6 +111,8 @@ void invoke_mixin() { var counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); var interaction = interactionService.currentInteraction().orElseThrow(); + { + // when wrapperFactory.wrapMixinT(Counter_bumpUsingMixin.class, counter1).act(); @@ -190,42 +196,46 @@ void invoke_mixin() { // ... and audit entries not yet generated var auditTrailEntries = auditTrailEntryRepository.findAll(); assertThat(auditTrailEntries).isEmpty(); + } // when - interactionService.nextInteraction(); // flushes the command and audit trail entries + testSupport.nextInteraction(ia->{ // flushes the command and audit trail entries + + // then + // ... command entry now marked as complete + var commandLogEntries = commandLogEntryRepository.findAll(); + assertThat(commandLogEntries).hasSize(1); + var commandLogEntryAfter = commandLogEntries.get(0); + assertThat(commandLogEntryAfter) + .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) + .satisfies(e -> assertThat(e.getDuration()).isNotNull()) + .satisfies(e -> assertThat(e.getResult()).isNotNull()) + .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK")); + + if(!isJpa()) { + // and then + // ... audit trail entry created + var auditTrailEntries = auditTrailEntryRepository.findAll(); + assertThat(auditTrailEntries).hasSize(1); + + var propertyIds = auditTrailEntries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).containsExactly("num"); + + var entriesById = auditTrailEntries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("cmdexecauditsess.test.Counter#num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isEqualTo(interaction.getInteractionId())) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); + } + + }); - // then - // ... command entry now marked as complete - commandLogEntries = commandLogEntryRepository.findAll(); - assertThat(commandLogEntries).hasSize(1); - var commandLogEntryAfter = commandLogEntries.get(0); - assertThat(commandLogEntryAfter) - .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) - .satisfies(e -> assertThat(e.getDuration()).isNotNull()) - .satisfies(e -> assertThat(e.getResult()).isNotNull()) - .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK")); - - if(!isJpa()) { - // and then - // ... audit trail entry created - auditTrailEntries = auditTrailEntryRepository.findAll(); - assertThat(auditTrailEntries).hasSize(1); - - var propertyIds = auditTrailEntries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).containsExactly("num"); - - var entriesById = auditTrailEntries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("cmdexecauditsess.test.Counter#num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isEqualTo(interaction.getInteractionId())) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); - } } @Test @@ -233,6 +243,7 @@ void invoke_direct() { // given var counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); + { // when wrapperFactory.wrap(counter1).bumpUsingDeclaredAction(); @@ -259,27 +270,29 @@ void invoke_direct() { // ... but audit entries not yet generated var auditTrailEntries = auditTrailEntryRepository.findAll(); assertThat(auditTrailEntries).isEmpty(); + } // when - interactionService.nextInteraction(); // flushes the command and audit trail entries - - // then - // ... command entry now marked as complete - commandLogEntries = commandLogEntryRepository.findAll(); - assertThat(commandLogEntries).hasSize(1); - var commandLogEntryAfter = commandLogEntries.get(0); - assertThat(commandLogEntryAfter) - .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) - .satisfies(e -> assertThat(e.getDuration()).isNotNull()) - .satisfies(e -> assertThat(e.getResult()).isNotNull()) - .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK")); - - if(!isJpa()) { - // and then - // ... audit trail entry created - auditTrailEntries = auditTrailEntryRepository.findAll(); - assertThat(auditTrailEntries).hasSize(1); - } + testSupport.nextInteraction(ia->{ // flushes the command and audit trail entries + + // then + // ... command entry now marked as complete + var commandLogEntries = commandLogEntryRepository.findAll(); + assertThat(commandLogEntries).hasSize(1); + var commandLogEntryAfter = commandLogEntries.get(0); + assertThat(commandLogEntryAfter) + .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) + .satisfies(e -> assertThat(e.getDuration()).isNotNull()) + .satisfies(e -> assertThat(e.getResult()).isNotNull()) + .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK")); + + if(!isJpa()) { + // and then + // ... audit trail entry created + var auditTrailEntries = auditTrailEntryRepository.findAll(); + assertThat(auditTrailEntries).hasSize(1); + } + }); } @@ -288,7 +301,7 @@ void edit() { // given var counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - + { // when wrapperFactory.wrap(counter1).setNum(99L); @@ -311,27 +324,28 @@ void edit() { // ... and audit entries not yet generated var auditTrailEntries = auditTrailEntryRepository.findAll(); assertThat(auditTrailEntries).isEmpty(); - - // when - interactionService.nextInteraction(); // flushes the command and audit trail entries - - // then - // ... command entry now marked as complete - commandLogEntries = commandLogEntryRepository.findAll(); - assertThat(commandLogEntries).hasSize(1); - var commandLogEntryAfter = commandLogEntries.get(0); - assertThat(commandLogEntryAfter) - .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) - .satisfies(e -> assertThat(e.getDuration()).isNotNull()) - .satisfies(e -> assertThat(e.getResult()).isNull()) // property edits are effectively void actions - .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK (VOID)")); - - if(!isJpa()) { - // and then - // ... audit trail entry created - auditTrailEntries = auditTrailEntryRepository.findAll(); - assertThat(auditTrailEntries).hasSize(1); } + // when + testSupport.nextInteraction(ia->{ // flushes the command and audit trail entries + + // then + // ... command entry now marked as complete + var commandLogEntries = commandLogEntryRepository.findAll(); + assertThat(commandLogEntries).hasSize(1); + var commandLogEntryAfter = commandLogEntries.get(0); + assertThat(commandLogEntryAfter) + .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) + .satisfies(e -> assertThat(e.getDuration()).isNotNull()) + .satisfies(e -> assertThat(e.getResult()).isNull()) // property edits are effectively void actions + .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK (VOID)")); + + if(!isJpa()) { + // and then + // ... audit trail entry created + var auditTrailEntries = auditTrailEntryRepository.findAll(); + assertThat(auditTrailEntries).hasSize(1); + } + }); } diff --git a/regressiontests/layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java b/regressiontests/layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java index 17f1fdbac40..eb26dc54a5b 100644 --- a/regressiontests/layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java +++ b/regressiontests/layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java @@ -52,6 +52,7 @@ import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.metamodel.MetaModelService; import org.apache.causeway.core.config.beans.CausewayBeanTypeRegistry; import org.apache.causeway.core.config.presets.CausewayPresets; @@ -123,16 +124,15 @@ static void beforeAll() { } Bookmark target1; + private TestSupport testSupport; @BeforeEach void beforeEach() { - interactionService.nextInteraction(); - - Optional bookmark = bookmarkService.bookmarkFor(newCounter("counter-1")); - target1 = bookmark.orElseThrow(); - - interactionService.nextInteraction(); - + this.testSupport = interactionService.testSupport(); + testSupport.nextInteraction(model->{ + Optional bookmark = bookmarkService.bookmarkFor(newCounter("counter-1")); + target1 = bookmark.orElseThrow(); + }); } protected Counter newCounter(final String name) { diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/CausewayInteractionHandler.java b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/CausewayInteractionHandler.java index cd1a1ea1c66..51f4fbea87b 100644 --- a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/CausewayInteractionHandler.java +++ b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/CausewayInteractionHandler.java @@ -47,12 +47,12 @@ public void beforeEach(final ExtensionContext extensionContext) throws Exception _Helper .getInteractionFactory(extensionContext) - .ifPresent(interactionService-> + .map(InteractionService::testSupport) + .ifPresent(testSupport-> _Helper .getCustomInteractionContext(extensionContext) - .ifPresentOrElse( - interactionService::openInteraction, - interactionService::openInteraction)); + .map(testSupport::openInteraction) + .orElseGet(testSupport::openInteraction)); } @Override diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/NoPermissionChecks.java b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/NoPermissionChecks.java index 00a9d0dec0d..ba0ff65fbf9 100644 --- a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/NoPermissionChecks.java +++ b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/NoPermissionChecks.java @@ -49,7 +49,7 @@ public void beforeEach(final ExtensionContext extensionContext) { interactionService.currentInteractionContext().ifPresent( currentInteractionContext -> { var sudoUser = currentInteractionContext.getUser().withRoleAdded(SudoService.ACCESS_ALL_ROLE.name()); - interactionService.openInteraction(currentInteractionContext.withUser(sudoUser)); + interactionService.testSupport().openInteraction(currentInteractionContext.withUser(sudoUser)); } ) ); diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/UserMementoRefiners.java b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/UserMementoRefiners.java index ebd64fdb387..1a97dbb1774 100644 --- a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/UserMementoRefiners.java +++ b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/UserMementoRefiners.java @@ -17,7 +17,6 @@ * under the License. * */ - package org.apache.causeway.testing.integtestsupport.applib; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -54,7 +53,7 @@ public void beforeEach(final ExtensionContext extensionContext) { for (UserMementoRefiner userMementoRefiner : serviceRegistry.select(UserMementoRefiner.class)) { user = userMementoRefiner.refine(user); } - interactionService.openInteraction(currentInteractionContext.withUser(user)); + interactionService.testSupport().openInteraction(currentInteractionContext.withUser(user)); } ) ) diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/AuthenticatedWebSessionForCauseway.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/AuthenticatedWebSessionForCauseway.java index 9b997f42af5..9e2f1d3f04b 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/AuthenticatedWebSessionForCauseway.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/AuthenticatedWebSessionForCauseway.java @@ -27,7 +27,7 @@ import org.apache.wicket.authroles.authorization.strategies.role.Roles; import org.apache.wicket.request.Request; import org.apache.wicket.request.cycle.RequestCycle; - +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.apache.causeway.applib.clock.VirtualClock; @@ -50,23 +50,17 @@ import org.apache.causeway.viewer.wicket.ui.pages.BookmarkedPagesModelProvider; import lombok.Getter; -import org.jspecify.annotations.NonNull; import lombok.extern.slf4j.Slf4j; /** * Viewer-specific implementation of {@link AuthenticatedWebSession}, which - * delegates to the Causeway' configured {@link AuthenticationManager}, and which - * also tracks thread usage (so that multiple concurrent requests are all + * delegates to the Causeway' configured {@link AuthenticationManager}, and + * which also tracks thread usage (so that multiple concurrent requests are all * associated with the same session). */ @Slf4j -public class AuthenticatedWebSessionForCauseway -extends AuthenticatedWebSession -implements - BreadcrumbModelProvider, - BookmarkedPagesModelProvider, - HasMetaModelContext, - HasAmendableInteractionContext { +public class AuthenticatedWebSessionForCauseway extends AuthenticatedWebSession implements BreadcrumbModelProvider, + BookmarkedPagesModelProvider, HasMetaModelContext, HasAmendableInteractionContext { private static final long serialVersionUID = 1L; @@ -78,21 +72,20 @@ public static AuthenticatedWebSessionForCauseway get() { * lazily populated in {@link #getBreadcrumbModel()} */ private BreadcrumbModel breadcrumbModel; + @Override public BreadcrumbModel getBreadcrumbModel() { - return breadcrumbModel != null - ? breadcrumbModel - : (breadcrumbModel = new BreadcrumbModel()); + return breadcrumbModel != null ? breadcrumbModel : (breadcrumbModel = new BreadcrumbModel()); } /** * lazily populated in {@link #getBookmarkedPagesModel()} */ private BookmarkedPagesModel bookmarkedPagesModel; + @Override public BookmarkedPagesModel getBookmarkedPagesModel() { - return bookmarkedPagesModel != null - ? bookmarkedPagesModel + return bookmarkedPagesModel != null ? bookmarkedPagesModel : (bookmarkedPagesModel = new BookmarkedPagesModel()); } @@ -100,23 +93,23 @@ public BookmarkedPagesModel getBookmarkedPagesModel() { * As populated in {@link #signIn(String, String)}. */ private InteractionContext interactionContext; + private void setInteractionContext(final @Nullable InteractionContext interactionContext) { - _Assert.assertFalse( - interactionContext !=null - && interactionContext.getUser().isImpersonating(), ()-> - "framework bug: cannot signin with an impersonated user"); + _Assert.assertFalse(interactionContext != null && interactionContext.getUser().isImpersonating(), + () -> "framework bug: cannot signin with an impersonated user"); this.interactionContext = interactionContext; } /** - * If there is an {@link InteractionContext} already (primed) - * (as some authentication mechanisms setup in filters, - * eg SpringSecurityFilter), then just use it. + * If there is an {@link InteractionContext} already (primed) (as some + * authentication mechanisms setup in filters, eg SpringSecurityFilter), then + * just use it. + * *

* However, for authorization, the authentication still must pass - * {@link AuthenticationManager} checks, - * as done in {@link #getInteractionContext()}, - * which on success also sets the signIn flag. + * {@link AuthenticationManager} checks, as done in + * {@link #getInteractionContext()}, which on success also sets the signIn flag. + * *

* Called by {@link WebRequestCycleForCauseway}. */ @@ -129,14 +122,14 @@ public void setPrimedInteractionContext(final @NonNull InteractionContext authen private String cachedSessionId; /** - * Optionally the current HttpSession's Id, - * based on whether such a session is available. - * @implNote side-effect free, that is, - * must not create a session if there is none yet + * Optionally the current HttpSession's Id, based on whether such a session is + * available. + * + * @implNote side-effect free, that is, must not create a session if there is + * none yet */ public Optional getCachedSessionId() { - if (cachedSessionId == null - && Session.exists()) { + if (cachedSessionId == null && Session.exists()) { cachedSessionId = getId(); } return Optional.ofNullable(cachedSessionId); @@ -156,9 +149,8 @@ public synchronized boolean authenticate(final String username, final String pas if (interactionContext != null) { log(SessionSubscriber.Type.LOGIN, username, null); return true; - } else { + } else return false; - } } @Override @@ -172,10 +164,8 @@ public synchronized void invalidateNow() { // principals for it to logout // - getAuthenticationManager().closeSession( - Optional.ofNullable(interactionContext) - .map(InteractionContext::getUser) - .orElse(null)); + getAuthenticationManager() + .closeSession(Optional.ofNullable(interactionContext).map(InteractionContext::getUser).orElse(null)); super.invalidateNow(); @@ -192,8 +182,7 @@ public synchronized void onInvalidate() { super.onInvalidate(); - var causedBy = RequestCycle.get() != null - ? SessionSubscriber.CausedBy.USER + var causedBy = RequestCycle.get() != null ? SessionSubscriber.CausedBy.USER : SessionSubscriber.CausedBy.SESSION_EXPIRATION; log(SessionSubscriber.Type.LOGOUT, userName, causedBy); @@ -205,23 +194,21 @@ public void amendInteractionContext(final UnaryOperator upda } /** - * Returns an {@link InteractionContext} either as authenticated (and then cached on the session subsequently), - * or taking into account {@link UserService impersonation}. + * Returns an {@link InteractionContext} either as authenticated (and then + * cached on the session subsequently), or taking into account + * {@link UserService impersonation}. + * *

- * The session must still {@link AuthenticationManager#isSessionValid(InteractionContext) be valid}, though - * note that this will always be true for externally authenticated users. + * The session must still + * {@link AuthenticationManager#isSessionValid(InteractionContext) be valid}, + * though note that this will always be true for externally authenticated users. */ - synchronized InteractionContext getInteractionContext() { - - if(interactionContext == null) { + synchronized InteractionContext getInteractionContext() { + if (interactionContext == null) return null; - } - if (Optional.ofNullable(getMetaModelContext()) - .map(MetaModelContext::getAuthenticationManager) - .filter(x -> x.isSessionValid(interactionContext)) - .isEmpty()) { + if (Optional.ofNullable(getMetaModelContext()).map(MetaModelContext::getAuthenticationManager) + .filter(x -> x.isSessionValid(interactionContext)).isEmpty()) return null; - } signIn(true); return interactionContext; @@ -229,50 +216,39 @@ synchronized InteractionContext getInteractionContext() { @Override public AuthenticationManager getAuthenticationManager() { - return Optional.ofNullable(getMetaModelContext()) - .map(MetaModelContext::getAuthenticationManager) - .orElse(null); + return Optional.ofNullable(getMetaModelContext()).map(MetaModelContext::getAuthenticationManager).orElse(null); } /** - * This is a no-op if the {@link #getInteractionContext() authentication session}'s - * {@link UserMemento#authenticationSource() source} is - * {@link AuthenticationSource#EXTERNAL external} - * (eg as managed by keycloak). + * This is a no-op if the {@link #getInteractionContext() authentication + * session}'s {@link UserMemento#authenticationSource() source} is + * {@link AuthenticationSource#EXTERNAL external} (eg as managed by keycloak). */ @Override public void invalidate() { - if(interactionContext !=null - && interactionContext.getUser().authenticationSource().isExternal()) { + if (interactionContext != null && interactionContext.getUser().authenticationSource().isExternal()) return; - } // otherwise super.invalidate(); } @Override public synchronized Roles getRoles() { - if (!isSignedIn()) { + if (!isSignedIn()) return null; - } - return getInteractionService() - .currentInteractionContext() - .map(InteractionContext::getUser) - .map(user->{ - var roles = new Roles(); - user.streamRoleNames() - .forEach(roles::add); - return roles; - }) - .orElse(null); + return getInteractionService().currentInteractionContext().map(InteractionContext::getUser).map(user -> { + var roles = new Roles(); + user.streamRoleNames().forEach(roles::add); + return roles; + }).orElse(null); } @Override public synchronized void detach() { - if(breadcrumbModel!=null) { + if (breadcrumbModel != null) { breadcrumbModel.detach(); } - if(bookmarkedPagesModel!=null) { + if (bookmarkedPagesModel != null) { bookmarkedPagesModel.detach(); } super.detach(); @@ -284,12 +260,10 @@ public void replaceSession() { // see https://issues.apache.org/jira/browse/CAUSEWAY-1018 } - private void log( - final SessionSubscriber.Type type, - final String username, + private void log(final SessionSubscriber.Type type, final String username, final SessionSubscriber.CausedBy causedBy) { - if(getMetaModelContext()==null) { + if (getMetaModelContext() == null) { log.warn("Failed to callback SessionLoggingServices due to unavailable MetaModelContext.\n" + "\tEvent Data: type={}, username={}, causedBy={}", type, username, causedBy); return; @@ -298,18 +272,16 @@ private void log( var interactionService = getInteractionService(); var sessionLoggingServices = getSessionLoggingServices(); - final Runnable loggingTask = ()->{ + final Runnable loggingTask = () -> { var now = virtualClock().nowAsJavaUtilDate(); - var httpSessionId = AuthenticatedWebSessionForCauseway.this.getCachedSessionId() - .orElse("(none)"); + var httpSessionId = AuthenticatedWebSessionForCauseway.this.getCachedSessionId().orElse("(none)"); - sessionLoggingServices - .forEach(sessionLoggingService -> - sessionLoggingService.log(type, username, now, causedBy, getSessionGuid(), httpSessionId)); + sessionLoggingServices.forEach(sessionLoggingService -> sessionLoggingService.log(type, username, now, + causedBy, getSessionGuid(), httpSessionId)); }; - if(interactionService!=null) { + if (interactionService != null) { interactionService.runAnonymous(loggingTask::run); } else { loggingTask.run(); @@ -322,9 +294,7 @@ protected Can getSessionLoggingServices() { private VirtualClock virtualClock() { try { - return getServiceRegistry() - .lookupService(ClockService.class) - .map(ClockService::getClock) + return getServiceRegistry().lookupService(ClockService.class).map(ClockService::getClock) .orElseGet(this::nowFallback); } catch (Exception e) { return nowFallback(); diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java new file mode 100644 index 00000000000..a7631deb424 --- /dev/null +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java @@ -0,0 +1,84 @@ +/* + * 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.viewer.wicket.viewer.integration; + +import org.apache.wicket.request.IRequestCycle; +import org.apache.wicket.request.IRequestHandler; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.cycle.RequestCycleContext; + +import org.apache.causeway.applib.services.iactnlayer.InteractionContext; +import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.user.UserService; +import org.apache.causeway.core.metamodel.context.MetaModelContext; + +import lombok.extern.slf4j.Slf4j; + +public class RequestCycle2 extends RequestCycle { + + public RequestCycle2(final RequestCycleContext context) { + super(context); + } + + record Wrapper(IRequestHandler delegate) implements IRequestHandler { + @Override + public void respond(final IRequestCycle requestCycle) { + delegate.respond(requestCycle); + } + @Override + public void detach(final IRequestCycle requestCycle) { + delegate.detach(requestCycle); + } + } + + @Slf4j + record RequestHandlerWrapper( + InteractionService interactionService, + InteractionContext interactionContext, + IRequestHandler delegate) implements IRequestHandler { + + @Override + public void respond(final IRequestCycle requestCycle) { + if(interactionContext==null) { + delegate.respond(requestCycle); + return; + } + interactionService.run(interactionContext, ()->delegate.respond(requestCycle)); + } + @Override + public void detach(final IRequestCycle requestCycle) { + delegate.detach(requestCycle); + } + } + + @Override + protected IRequestHandler resolveRequestHandler() { + var mmc = MetaModelContext.instanceElseFail(); + + var ic = new SessionAuthenticator(mmc.getInteractionService(), mmc.lookupServiceElseFail(UserService.class)) + .determineInteractionContext() + .orElse(null); + + return new RequestHandlerWrapper( + mmc.getInteractionService(), + ic, + super.resolveRequestHandler()); + } + +} diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/SessionAuthenticator.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/SessionAuthenticator.java new file mode 100644 index 00000000000..85f726aeec7 --- /dev/null +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/SessionAuthenticator.java @@ -0,0 +1,61 @@ +/* + * 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.viewer.wicket.viewer.integration; + +import java.util.Optional; + +import org.apache.causeway.applib.services.iactnlayer.InteractionContext; +import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.applib.services.user.UserService; +import org.apache.causeway.commons.internal.exceptions._Exceptions; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +record SessionAuthenticator( + InteractionService interactionService, + UserService userService) { + + public Optional determineInteractionContext() { + // participate if an InteractionContext was already provided through some other mechanism, + // but fail early if the current user is impersonating + // (seeing this if going back the browser history into a page, that was previously impersonated) + var session = AuthenticatedWebSessionForCauseway.get(); + + interactionService.currentInteractionContext() + .ifPresent(ic->{ + if(ic.getUser().isImpersonating()) + throw _Exceptions.illegalState("cannot enter a new request cycle with a left over impersonating user"); + session.setPrimedInteractionContext(ic); + }); + + var interactionContext = session.getInteractionContext(); + if (interactionContext == null) { + log.warn("onBeginRequest out - session was not opened (because no authentication)"); + return Optional.empty(); + } + + return Optional.of( + userService + .lookupImpersonatedUser() + .map(interactionContext::withUser) + .orElse(interactionContext)); + } + +} diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java index 6b04e2e1a0f..a4240799c16 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java @@ -54,9 +54,7 @@ import org.apache.causeway.applib.services.exceprecog.Recognition; import org.apache.causeway.applib.services.i18n.TranslationContext; import org.apache.causeway.applib.services.iactn.Interaction; -import org.apache.causeway.applib.services.iactnlayer.InteractionService; import org.apache.causeway.applib.services.metrics.MetricsService; -import org.apache.causeway.applib.services.user.UserService; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.commons.internal.base._Timing; @@ -119,7 +117,7 @@ static boolean isExpiryMessageTimeframeExpired() { } private static final MetaDataKey SESSION_LIFECYCLE_PHASE_KEY = - new MetaDataKey() { private static final long serialVersionUID = 1L; }; + new MetaDataKey<>() { private static final long serialVersionUID = 1L; }; @Setter private PageClassRegistry pageClassRegistry; @@ -142,50 +140,11 @@ public synchronized void onBeginRequest(final RequestCycle requestCycle) { log.trace("flagging the RequestCycle as expired (rememberMe feature is active for the current user)"); } } - if(log.isTraceEnabled()) { - log.trace("onBeginRequest out - session was not opened (because no Session)"); - } - return; - } - - // participate if an InteractionContext was already provided through some other mechanism, - // but fail early if the current user is impersonating - // (seeing this if going back the browser history into a page, that was previously impersonated) - var interactionService = getInteractionService(); - var authenticatedWebSession = AuthenticatedWebSessionForCauseway.get(); - - /*XXX for debugging delegated user ... - interactionService.openInteraction(InteractionContext - .ofUserWithSystemDefaults( - UserMemento.ofName("delegated") - .withRoleAdded(UserMemento.AUTHORIZED_USER_ROLE) - .withAuthenticationSource(AuthenticationSource.EXTERNAL)));*/ - - var currentInteractionContext = interactionService.currentInteractionContext(); - if(currentInteractionContext.isPresent()) { - if(currentInteractionContext.get().getUser().isImpersonating()) { - throw _Exceptions.illegalState("cannot enter a new request cycle with a left over impersonating user"); - } - authenticatedWebSession.setPrimedInteractionContext(currentInteractionContext.get()); - } - - var interactionContext0 = authenticatedWebSession.getInteractionContext(); - if (interactionContext0 == null) { - log.warn("onBeginRequest out - session was not opened (because no authentication)"); return; } - - // impersonation support - var interactionContext1 = lookupServiceElseFail(UserService.class) - .lookupImpersonatedUser() - .map(sudoUser -> interactionContext0.withUser(sudoUser)) - .orElse(interactionContext0); - - // Note: this is a no-op if an interactionContext layer was already opened and is unchanged. - interactionService.openInteraction(interactionContext1); - + if(log.isTraceEnabled()) { - log.trace("onBeginRequest out - session was opened"); + log.trace("onBeginRequest out - session about to open"); } if(log.isDebugEnabled()) { @@ -212,19 +171,17 @@ public void onRequestHandlerResolved(final RequestCycle requestCycle, final IReq } SessionLifecyclePhase.transferExpiredFlagToSession(); - } else if(handler instanceof RenderPageRequestHandler) { + } else if(handler instanceof RenderPageRequestHandler requestHandler) { // using side-effect free access to MM validation result var validationResult = getMetaModelContext().getSpecificationLoader().getValidationResult() .orElseThrow(()->_Exceptions.illegalState("Application is not fully initialized yet.")); if(validationResult.hasFailures()) { - RenderPageRequestHandler requestHandler = (RenderPageRequestHandler) handler; final IRequestablePage nextPage = requestHandler.getPage(); - if(nextPage instanceof ErrorPage || nextPage instanceof MmvErrorPage) { + if(nextPage instanceof ErrorPage || nextPage instanceof MmvErrorPage) // do nothing return; - } throw new MetaModelInvalidException(validationResult.getAsLineNumberedString()); } @@ -291,9 +248,6 @@ public synchronized void onEndRequest(final RequestCycle requestCycle) { } } - getMetaModelContext().lookupService(InteractionService.class).ifPresent( - InteractionService::closeInteractionLayers - ); } @Override @@ -321,8 +275,7 @@ public IRequestHandler onException(final RequestCycle cycle, final Exception ex) try { // adapted from http://markmail.org/message/un7phzjbtmrrperc - if(ex instanceof ListenerInvocationNotAllowedException) { - final ListenerInvocationNotAllowedException linaex = (ListenerInvocationNotAllowedException) ex; + if(ex instanceof final ListenerInvocationNotAllowedException linaex) { if(linaex.getComponent() != null && PromptFormAbstract.ID_CANCEL_BUTTON.equals(linaex.getComponent().getId())) { // no message. // this seems to occur when press ESC twice in rapid succession on a modal dialog. @@ -439,9 +392,8 @@ protected IRequestablePage errorPageFor(final Exception ex) { var validationResult = mmc.getSpecificationLoader().getValidationResult() .orElse(null); if(validationResult!=null - && validationResult.hasFailures()) { + && validationResult.hasFailures()) return new MmvErrorPage(validationResult.getMessages("[%d] %s")); - } var exceptionRecognizerService = mmc.getServiceRegistry() .lookupServiceElseFail(ExceptionRecognizerService.class); @@ -499,9 +451,8 @@ private IRequestablePage newSignInPage(final ExceptionModel exceptionModel) { * Matters should improve once CAUSEWAY-299 gets implemented... */ protected boolean isSignedIn() { - if(!isInInteraction()) { + if(!isInInteraction()) return false; - } return getWicketAuthenticatedWebSession().isSignedIn(); } @@ -514,9 +465,8 @@ private boolean userHasSessionWithRememberMe(final RequestCycle requestCycle) { getConfiguration().viewer().wicket().rememberMe().cookieKey()); for (var cookie : cookies) { - if (cookieKey.equals(cookie.getName())) { + if (cookieKey.equals(cookie.getName())) return true; - } } } return false; diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketAjaxRequestListenerUtil.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketAjaxRequestListenerUtil.java index d155e62fd94..6a7c3ed4b11 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketAjaxRequestListenerUtil.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketAjaxRequestListenerUtil.java @@ -27,7 +27,6 @@ import org.apache.wicket.request.Request; import org.apache.wicket.request.component.IRequestablePage; -import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract; import lombok.experimental.UtilityClass; @@ -36,8 +35,7 @@ public final class CausewayWicketAjaxRequestListenerUtil { public void setRootRequestMapper( - final WebApplication app, - final MetaModelContext commonContext) { + final WebApplication app) { app.setRootRequestMapper(new SystemMapper(app) { @Override @@ -55,8 +53,7 @@ public IRequestHandler mapRequest(final Request request) { final IRequestablePage iRequestablePage = ((ListenerRequestHandler)handler).getPage(); - if(iRequestablePage instanceof PageAbstract) { - var pageAbstract = (PageAbstract) iRequestablePage; + if(iRequestablePage instanceof PageAbstract pageAbstract) { pageAbstract.onNewRequestCycle(); } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java index 6dbfe9c7d07..706c9855140 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java @@ -68,6 +68,7 @@ import org.apache.causeway.viewer.wicket.viewer.integration.AuthenticatedWebSessionForCauseway; import org.apache.causeway.viewer.wicket.viewer.integration.CausewayResourceSettings; import org.apache.causeway.viewer.wicket.viewer.integration.ConverterForObjectAdapter; +import org.apache.causeway.viewer.wicket.viewer.integration.RequestCycle2; import org.apache.causeway.viewer.wicket.viewer.integration.WebRequestCycleForCauseway; import lombok.Getter; @@ -136,11 +137,11 @@ protected void internalInit() { // in which search for i18n properties, to search for the application-specific // settings before any other. setResourceSettings(new CausewayResourceSettings(this)); - super.internalInit(); + setRequestCycleProvider(RequestCycle2::new); // intercept AJAX requests and reload view-models so any detached entities are re-fetched - CausewayWicketAjaxRequestListenerUtil.setRootRequestMapper(this, metaModelContext); + CausewayWicketAjaxRequestListenerUtil.setRootRequestMapper(this); } private AjaxRequestTarget decorate(final AjaxRequestTarget ajaxRequestTarget) { @@ -328,9 +329,8 @@ protected void mountPage(final String mountPath, final PageType pageType) { @Override public final RuntimeConfigurationType getConfigurationType() { - if(systemEnvironment==null) { + if(systemEnvironment==null) return RuntimeConfigurationType.DEPLOYMENT; - } return systemEnvironment.isPrototyping() ? RuntimeConfigurationType.DEVELOPMENT From bcc98caaa6cbb42bae37d0dc0a026dd0a5e9ecff Mon Sep 17 00:00:00 2001 From: andi-huber Date: Fri, 20 Mar 2026 23:38:59 +0100 Subject: [PATCH 03/15] CAUSEWAY-3975: consolidating custom request processing logic (wicket) --- .../conf/Configuration_usingWicket.java | 5 +- ...uestCycle2.java => RootRequestMapper.java} | 67 +++++++++----- .../WebRequestCycleForCauseway.java | 10 +- ...CausewayWicketAjaxRequestListenerUtil.java | 91 ------------------- .../wicketapp/CausewayWicketApplication.java | 7 +- 5 files changed, 56 insertions(+), 124 deletions(-) rename viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/{RequestCycle2.java => RootRequestMapper.java} (54%) delete mode 100644 viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketAjaxRequestListenerUtil.java diff --git a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java index b535c6edda3..e8c488746d9 100644 --- a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java +++ b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java @@ -61,7 +61,7 @@ import org.apache.causeway.viewer.wicket.ui.pages.PageClassRegistry; import org.apache.causeway.viewer.wicket.ui.pages.obj.DomainObjectPage; import org.apache.causeway.viewer.wicket.viewer.CausewayModuleViewerWicketViewer; -import org.apache.causeway.viewer.wicket.viewer.wicketapp.CausewayWicketAjaxRequestListenerUtil; +import org.apache.causeway.viewer.wicket.viewer.integration.RootRequestMapper; import lombok.AccessLevel; import lombok.Getter; @@ -349,8 +349,7 @@ protected IPageFactory newPageFactory() { @Override protected void internalInit() { super.internalInit(); - // intercept AJAX requests and reload view-models so any detached entities are re-fetched - CausewayWicketAjaxRequestListenerUtil.setRootRequestMapper(this); + setRootRequestMapper(new RootRequestMapper(this)); } } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java similarity index 54% rename from viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java rename to viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java index a7631deb424..1d50d625120 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java @@ -18,34 +18,26 @@ */ package org.apache.causeway.viewer.wicket.viewer.integration; +import org.apache.wicket.Application; +import org.apache.wicket.SystemMapper; +import org.apache.wicket.core.request.handler.ListenerRequestHandler; +import org.apache.wicket.core.request.mapper.PageInstanceMapper; import org.apache.wicket.request.IRequestCycle; import org.apache.wicket.request.IRequestHandler; -import org.apache.wicket.request.cycle.RequestCycle; -import org.apache.wicket.request.cycle.RequestCycleContext; +import org.apache.wicket.request.IRequestMapper; +import org.apache.wicket.request.Request; +import org.apache.wicket.request.component.IRequestablePage; import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.applib.services.user.UserService; import org.apache.causeway.core.metamodel.context.MetaModelContext; +import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract; import lombok.extern.slf4j.Slf4j; -public class RequestCycle2 extends RequestCycle { +public final class RootRequestMapper extends SystemMapper implements IRequestMapper { - public RequestCycle2(final RequestCycleContext context) { - super(context); - } - - record Wrapper(IRequestHandler delegate) implements IRequestHandler { - @Override - public void respond(final IRequestCycle requestCycle) { - delegate.respond(requestCycle); - } - @Override - public void detach(final IRequestCycle requestCycle) { - delegate.detach(requestCycle); - } - } + public static ThreadLocal X = new ThreadLocal<>(); @Slf4j record RequestHandlerWrapper( @@ -67,18 +59,43 @@ public void detach(final IRequestCycle requestCycle) { } } + public RootRequestMapper(final Application application) { + super(application); + } + @Override - protected IRequestHandler resolveRequestHandler() { + public IRequestHandler mapRequest(final Request request) { var mmc = MetaModelContext.instanceElseFail(); - - var ic = new SessionAuthenticator(mmc.getInteractionService(), mmc.lookupServiceElseFail(UserService.class)) - .determineInteractionContext() - .orElse(null); +// var ic = new SessionAuthenticator(mmc.getInteractionService(), mmc.lookupServiceElseFail(UserService.class)) +// .determineInteractionContext() +// .orElse(null); return new RequestHandlerWrapper( mmc.getInteractionService(), - ic, - super.resolveRequestHandler()); + X.get(), + super.mapRequest(request)); + } + + // intercept AJAX requests and reload view-models so any detached entities are re-fetched + @Override + protected IRequestMapper newPageInstanceMapper() { + return new PageInstanceMapper() { + @Override + public IRequestHandler mapRequest(final Request request) { + var handler = super.mapRequest(request); + + if (handler instanceof ListenerRequestHandler) { + + final IRequestablePage iRequestablePage = ((ListenerRequestHandler) handler).getPage(); + + if (iRequestablePage instanceof PageAbstract pageAbstract) { + pageAbstract.onNewRequestCycle(); + } + } + + return handler; + } + }; } } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java index a4240799c16..5f52bc2a2ba 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java @@ -55,12 +55,14 @@ import org.apache.causeway.applib.services.i18n.TranslationContext; import org.apache.causeway.applib.services.iactn.Interaction; import org.apache.causeway.applib.services.metrics.MetricsService; +import org.apache.causeway.applib.services.user.UserService; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.commons.internal.base._Timing; import org.apache.causeway.commons.internal.base._Timing.StopWatch; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.context.HasMetaModelContext; +import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.core.metamodel.spec.feature.ObjectMember; import org.apache.causeway.core.metamodel.specloader.validator.MetaModelInvalidException; import org.apache.causeway.viewer.commons.model.error.ExceptionModel; @@ -142,7 +144,13 @@ public synchronized void onBeginRequest(final RequestCycle requestCycle) { } return; } - + + var mmc = MetaModelContext.instanceElseFail(); + var ic = new SessionAuthenticator(mmc.getInteractionService(), mmc.lookupServiceElseFail(UserService.class)) + .determineInteractionContext() + .orElse(null); + RootRequestMapper.X.set(ic); + if(log.isTraceEnabled()) { log.trace("onBeginRequest out - session about to open"); } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketAjaxRequestListenerUtil.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketAjaxRequestListenerUtil.java deleted file mode 100644 index 6a7c3ed4b11..00000000000 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketAjaxRequestListenerUtil.java +++ /dev/null @@ -1,91 +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.viewer.wicket.viewer.wicketapp; - -import org.apache.wicket.SystemMapper; -import org.apache.wicket.core.request.handler.ListenerRequestHandler; -import org.apache.wicket.core.request.mapper.PageInstanceMapper; -import org.apache.wicket.protocol.http.WebApplication; -import org.apache.wicket.request.IRequestHandler; -import org.apache.wicket.request.IRequestMapper; -import org.apache.wicket.request.Request; -import org.apache.wicket.request.component.IRequestablePage; - -import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public final class CausewayWicketAjaxRequestListenerUtil { - - public void setRootRequestMapper( - final WebApplication app) { - - app.setRootRequestMapper(new SystemMapper(app) { - @Override - protected IRequestMapper newPageInstanceMapper() { - return new PageInstanceMapper() { - @Override - public IRequestHandler mapRequest(final Request request) { - var handler = super.mapRequest(request); - //final boolean isAjax = ((WebRequest)request).isAjax(); - - if(handler instanceof ListenerRequestHandler) { -// _Debug.log("AJAX via ListenerRequestHandler"); -// RequestCycle.get().getListeners().add(newRequestCycleListener()); - - final IRequestablePage iRequestablePage = - ((ListenerRequestHandler)handler).getPage(); - - if(iRequestablePage instanceof PageAbstract pageAbstract) { - pageAbstract.onNewRequestCycle(); - } - - } - - return handler; - } - }; - } - }); - } - -// public IListener newAjaxListener() { -// -// RequestCycle x; -// -// return new IListener() {; -// @Override -// public void onBeforeRespond(final Map map, final AjaxRequestTarget target) { -// _Debug.log("AJAX via IListener"); -// DomainObjectPage.broadcastAjaxRequest(target.getPage(), target); -// } -// }; -// } - -// private IRequestCycleListener newRequestCycleListener() { -// return new IRequestCycleListener() { -// @Override -// public void onRequestHandlerResolved(final RequestCycle cycle, final IRequestHandler handler) { -// _Debug.log("RequestCycle: handler resolved %s", handler); -// } -// }; -// } - -} diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java index 706c9855140..41bb10f8294 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java @@ -68,7 +68,7 @@ import org.apache.causeway.viewer.wicket.viewer.integration.AuthenticatedWebSessionForCauseway; import org.apache.causeway.viewer.wicket.viewer.integration.CausewayResourceSettings; import org.apache.causeway.viewer.wicket.viewer.integration.ConverterForObjectAdapter; -import org.apache.causeway.viewer.wicket.viewer.integration.RequestCycle2; +import org.apache.causeway.viewer.wicket.viewer.integration.RootRequestMapper; import org.apache.causeway.viewer.wicket.viewer.integration.WebRequestCycleForCauseway; import lombok.Getter; @@ -138,10 +138,9 @@ protected void internalInit() { // settings before any other. setResourceSettings(new CausewayResourceSettings(this)); super.internalInit(); - setRequestCycleProvider(RequestCycle2::new); + //setRequestCycleProvider(RequestCycle2::new); - // intercept AJAX requests and reload view-models so any detached entities are re-fetched - CausewayWicketAjaxRequestListenerUtil.setRootRequestMapper(this); + setRootRequestMapper(new RootRequestMapper(this)); } private AjaxRequestTarget decorate(final AjaxRequestTarget ajaxRequestTarget) { From 564ea072a841103c9cdd3ecab07167fcec085514 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 00:09:20 +0100 Subject: [PATCH 04/15] CAUSEWAY-3975: interaction is too short lived - using the old method instead --- .../viewer/integration/RootRequestMapper.java | 22 ++++++++++++++----- .../WebRequestCycleForCauseway.java | 2 ++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java index 1d50d625120..9f3c1204fe5 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java @@ -30,6 +30,7 @@ import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionService; +import org.apache.causeway.commons.internal.base._Lazy; import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract; @@ -43,19 +44,30 @@ public final class RootRequestMapper extends SystemMapper implements IRequestMap record RequestHandlerWrapper( InteractionService interactionService, InteractionContext interactionContext, - IRequestHandler delegate) implements IRequestHandler { + _Lazy delegate) implements IRequestHandler { @Override public void respond(final IRequestCycle requestCycle) { if(interactionContext==null) { - delegate.respond(requestCycle); + delegate.get().respond(requestCycle); return; } - interactionService.run(interactionContext, ()->delegate.respond(requestCycle)); + interactionService.testSupport().openInteraction(interactionContext); + X.remove(); + delegate.get().respond(requestCycle); + + +// interactionService.run(interactionContext, ()->{ +// delegate.get().respond(requestCycle); +// X.remove(); +// }); } @Override public void detach(final IRequestCycle requestCycle) { - delegate.detach(requestCycle); + if(delegate.isMemoized()) { + delegate.get().detach(requestCycle); + } + interactionService.closeInteractionLayers(); } } @@ -73,7 +85,7 @@ public IRequestHandler mapRequest(final Request request) { return new RequestHandlerWrapper( mmc.getInteractionService(), X.get(), - super.mapRequest(request)); + _Lazy.threadSafe(()->super.mapRequest(request))); } // intercept AJAX requests and reload view-models so any detached entities are re-fetched diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java index 5f52bc2a2ba..b1fb6b7a8fe 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java @@ -437,6 +437,8 @@ private IRequestablePage newSignInPage(final ExceptionModel exceptionModel) { signInPageClass = WicketSignInPage.class; } final PageParameters parameters = new PageParameters(); + if(signInPageClass == WicketSignInPage.class) + return new WicketSignInPage(parameters, exceptionModel); Page signInPage; try { Constructor constructor = signInPageClass.getConstructor(PageParameters.class, ExceptionModel.class); From d6b3b3ba8408e08939300760265c0cf55d03d867 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 08:17:09 +0100 Subject: [PATCH 05/15] CAUSEWAY-3975: interaction creation fixes --- .../viewer/integration/RequestCycle2.java | 57 ++++++++++ .../viewer/integration/RootRequestMapper.java | 66 +---------- .../integration/SessionAuthenticator.java | 3 +- .../integration/TelemetryStartHandler.java | 51 +++++++++ .../integration/TelemetryStopHandler.java | 47 ++++++++ .../WebRequestCycleForCauseway.java | 107 ++++-------------- .../wicketapp/CausewayWicketApplication.java | 31 ++--- 7 files changed, 201 insertions(+), 161 deletions(-) create mode 100644 viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java create mode 100644 viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java create mode 100644 viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java new file mode 100644 index 00000000000..7efdc44585c --- /dev/null +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java @@ -0,0 +1,57 @@ +/* + * 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.viewer.wicket.viewer.integration; + +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.cycle.RequestCycleContext; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.Observation.Scope; + +public class RequestCycle2 extends RequestCycle { + + final long startTimeNanos; + Observation observation; + Scope scope; + + public RequestCycle2(final RequestCycleContext context) { + super(context); + this.startTimeNanos = System.nanoTime(); + } + + long millisSinceStart() { + return (System.nanoTime() - startTimeNanos)/1000_000; + } + + void observationStartAndOpenScope() { + if(observation==null) return; + observation.start(); + this.scope = observation.openScope(); + } + + void observationCloseScopeAndStop() { + if(observation==null) return; + if(scope!=null) { + this.scope.close(); + this.scope = null; + } + observation.stop(); + } + +} diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java index 9f3c1204fe5..7e6b7cfd9c7 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java @@ -22,72 +22,18 @@ import org.apache.wicket.SystemMapper; import org.apache.wicket.core.request.handler.ListenerRequestHandler; import org.apache.wicket.core.request.mapper.PageInstanceMapper; -import org.apache.wicket.request.IRequestCycle; import org.apache.wicket.request.IRequestHandler; import org.apache.wicket.request.IRequestMapper; import org.apache.wicket.request.Request; -import org.apache.wicket.request.component.IRequestablePage; -import org.apache.causeway.applib.services.iactnlayer.InteractionContext; -import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.commons.internal.base._Lazy; -import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract; -import lombok.extern.slf4j.Slf4j; - public final class RootRequestMapper extends SystemMapper implements IRequestMapper { - public static ThreadLocal X = new ThreadLocal<>(); - - @Slf4j - record RequestHandlerWrapper( - InteractionService interactionService, - InteractionContext interactionContext, - _Lazy delegate) implements IRequestHandler { - - @Override - public void respond(final IRequestCycle requestCycle) { - if(interactionContext==null) { - delegate.get().respond(requestCycle); - return; - } - interactionService.testSupport().openInteraction(interactionContext); - X.remove(); - delegate.get().respond(requestCycle); - - -// interactionService.run(interactionContext, ()->{ -// delegate.get().respond(requestCycle); -// X.remove(); -// }); - } - @Override - public void detach(final IRequestCycle requestCycle) { - if(delegate.isMemoized()) { - delegate.get().detach(requestCycle); - } - interactionService.closeInteractionLayers(); - } - } - public RootRequestMapper(final Application application) { super(application); } - @Override - public IRequestHandler mapRequest(final Request request) { - var mmc = MetaModelContext.instanceElseFail(); -// var ic = new SessionAuthenticator(mmc.getInteractionService(), mmc.lookupServiceElseFail(UserService.class)) -// .determineInteractionContext() -// .orElse(null); - - return new RequestHandlerWrapper( - mmc.getInteractionService(), - X.get(), - _Lazy.threadSafe(()->super.mapRequest(request))); - } - // intercept AJAX requests and reload view-models so any detached entities are re-fetched @Override protected IRequestMapper newPageInstanceMapper() { @@ -95,16 +41,10 @@ protected IRequestMapper newPageInstanceMapper() { @Override public IRequestHandler mapRequest(final Request request) { var handler = super.mapRequest(request); - - if (handler instanceof ListenerRequestHandler) { - - final IRequestablePage iRequestablePage = ((ListenerRequestHandler) handler).getPage(); - - if (iRequestablePage instanceof PageAbstract pageAbstract) { - pageAbstract.onNewRequestCycle(); - } + if (handler instanceof ListenerRequestHandler listenerRequestHandler + && listenerRequestHandler.getPage() instanceof PageAbstract pageAbstract) { + pageAbstract.onNewRequestCycle(); } - return handler; } }; diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/SessionAuthenticator.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/SessionAuthenticator.java index 85f726aeec7..4d1e223f174 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/SessionAuthenticator.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/SessionAuthenticator.java @@ -47,10 +47,11 @@ public Optional determineInteractionContext() { var interactionContext = session.getInteractionContext(); if (interactionContext == null) { - log.warn("onBeginRequest out - session was not opened (because no authentication)"); + log.warn("session was not opened (because not authenticated)"); return Optional.empty(); } + // impersonation support return Optional.of( userService .lookupImpersonatedUser() diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java new file mode 100644 index 00000000000..b5eb4badd62 --- /dev/null +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java @@ -0,0 +1,51 @@ +/* + * 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.viewer.wicket.viewer.integration; + +import org.apache.wicket.request.IRequestHandler; +import org.apache.wicket.request.cycle.IRequestCycleListener; +import org.apache.wicket.request.cycle.RequestCycle; + +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal.ObservationProvider; + +/** + * @since 4.0 + */ +public record TelemetryStartHandler( + ObservationProvider observationProvider) +implements IRequestCycleListener { + + @Override + public synchronized void onBeginRequest(final RequestCycle requestCycle) { + if (requestCycle instanceof RequestCycle2 requestCycle2) { + requestCycle2.observation = observationProvider.get("Apache Wicket Request Cycle"); + requestCycle2.observationStartAndOpenScope(); + } + } + + @Override + public IRequestHandler onException(final RequestCycle requestCycle, final Exception ex) { + if (requestCycle instanceof RequestCycle2 requestCycle2 + && requestCycle2.observation!=null) { + requestCycle2.observation.error(ex); + } + return null; + } + +} diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java new file mode 100644 index 00000000000..d33a11f1ff0 --- /dev/null +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java @@ -0,0 +1,47 @@ +/* + * 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.viewer.wicket.viewer.integration; + +import org.apache.wicket.request.cycle.IRequestCycleListener; +import org.apache.wicket.request.cycle.RequestCycle; + +import org.apache.causeway.applib.services.metrics.MetricsService; + +/** + * @since 4.0 + */ +public record TelemetryStopHandler( + MetricsService metricsService) +implements IRequestCycleListener { + + @Override + public void onEndRequest(final RequestCycle requestCycle) { + if (requestCycle instanceof RequestCycle2 requestCycle2 + && requestCycle2.observation!=null) { + + if(requestCycle2.millisSinceStart() > 50) { // avoid clutter + requestCycle2.observation.highCardinalityKeyValue("numberEntitiesLoaded", ""+metricsService.numberEntitiesLoaded()); + requestCycle2.observation.highCardinalityKeyValue("numberEntitiesDirtied", ""+metricsService.numberEntitiesDirtied()); + } + + requestCycle2.observation.stop(); + } + } + +} diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java index b1fb6b7a8fe..b2f1650080e 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java @@ -54,16 +54,15 @@ import org.apache.causeway.applib.services.exceprecog.Recognition; import org.apache.causeway.applib.services.i18n.TranslationContext; import org.apache.causeway.applib.services.iactn.Interaction; -import org.apache.causeway.applib.services.metrics.MetricsService; +import org.apache.causeway.applib.services.iactnlayer.InteractionService; import org.apache.causeway.applib.services.user.UserService; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.internal.base._Strings; -import org.apache.causeway.commons.internal.base._Timing; -import org.apache.causeway.commons.internal.base._Timing.StopWatch; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.context.HasMetaModelContext; import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.core.metamodel.spec.feature.ObjectMember; +import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; import org.apache.causeway.core.metamodel.specloader.validator.MetaModelInvalidException; import org.apache.causeway.viewer.commons.model.error.ExceptionModel; import org.apache.causeway.viewer.wicket.model.models.PageType; @@ -73,7 +72,6 @@ import org.apache.causeway.viewer.wicket.ui.pages.mmverror.MmvErrorPage; import org.apache.causeway.viewer.wicket.ui.panels.PromptFormAbstract; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** @@ -84,7 +82,12 @@ * @since 2.0 */ @Slf4j -public class WebRequestCycleForCauseway +public record WebRequestCycleForCauseway( + InteractionService interactionService, + PageClassRegistry pageClassRegistry, + ExceptionRecognizerService exceptionRecognizerService, + SpecificationLoader specificationLoader, + SessionAuthenticator sessionAuthenticator) implements HasMetaModelContext, IRequestCycleListener { @@ -121,17 +124,14 @@ static boolean isExpiryMessageTimeframeExpired() { private static final MetaDataKey SESSION_LIFECYCLE_PHASE_KEY = new MetaDataKey<>() { private static final long serialVersionUID = 1L; }; - @Setter - private PageClassRegistry pageClassRegistry; - - private static ThreadLocal timings = ThreadLocal.withInitial(_Timing::now); + public WebRequestCycleForCauseway(final MetaModelContext mmc, final PageClassRegistry pageClassRegistry) { + this(mmc.getInteractionService(), pageClassRegistry, mmc.lookupServiceElseFail(ExceptionRecognizerService.class), + mmc.getSpecificationLoader(), + new SessionAuthenticator(mmc.getInteractionService(), mmc.lookupServiceElseFail(UserService.class))); + } @Override - public synchronized void onBeginRequest(final RequestCycle requestCycle) { - - if(log.isTraceEnabled()) { - log.trace("onBeginRequest in"); - } + public void onBeginRequest(final RequestCycle requestCycle) { if (!Session.exists()) { // Track if session was created from an expired one to notify user of the refresh. @@ -145,19 +145,8 @@ public synchronized void onBeginRequest(final RequestCycle requestCycle) { return; } - var mmc = MetaModelContext.instanceElseFail(); - var ic = new SessionAuthenticator(mmc.getInteractionService(), mmc.lookupServiceElseFail(UserService.class)) - .determineInteractionContext() - .orElse(null); - RootRequestMapper.X.set(ic); - - if(log.isTraceEnabled()) { - log.trace("onBeginRequest out - session about to open"); - } - - if(log.isDebugEnabled()) { - timings.set(_Timing.now()); - } + sessionAuthenticator.determineInteractionContext() + .ifPresent(interactionService.testSupport()::openInteraction); } @Override @@ -182,7 +171,7 @@ public void onRequestHandlerResolved(final RequestCycle requestCycle, final IReq } else if(handler instanceof RenderPageRequestHandler requestHandler) { // using side-effect free access to MM validation result - var validationResult = getMetaModelContext().getSpecificationLoader().getValidationResult() + var validationResult = specificationLoader().getValidationResult() .orElseThrow(()->_Exceptions.illegalState("Application is not fully initialized yet.")); if(validationResult.hasFailures()) { @@ -217,9 +206,6 @@ public void onRequestHandlerResolved(final RequestCycle requestCycle, final IReq } } - if(log.isTraceEnabled()) { - log.trace("onRequestHandlerResolved out"); - } } /** @@ -238,41 +224,14 @@ public void onRequestHandlerExecuted(final RequestCycle requestCycle, final IReq */ @Override public synchronized void onEndRequest(final RequestCycle requestCycle) { - - if(log.isDebugEnabled()) { - var metricsServiceIfAny = getMetaModelContext().lookupService(MetricsService.class); - long took = timings.get().getMillis(); - if(took > 50) { // avoid too much clutter - if(metricsServiceIfAny.isPresent()) { - var metricsService = metricsServiceIfAny.get(); - int numberEntitiesLoaded = metricsService.numberEntitiesLoaded(); - int numberEntitiesDirtied = metricsService.numberEntitiesDirtied(); - if(numberEntitiesLoaded > 0 || numberEntitiesDirtied > 0) { - log.debug("onEndRequest took: {}ms numberEntitiesLoaded: {}, numberEntitiesDirtied: {}", took, numberEntitiesLoaded, numberEntitiesDirtied); - } - } else { - log.debug("onEndRequest took: {}ms", took); - } - } - } - - } - - @Override - public void onDetach(final RequestCycle requestCycle) { - // detach the current @RequestScope, if any - IRequestCycleListener.super.onDetach(requestCycle); + interactionService.closeInteractionLayers(); } @Override public IRequestHandler onException(final RequestCycle cycle, final Exception ex) { - if(log.isDebugEnabled()) { - log.debug("onException {} took: {}ms", ex.getClass().getSimpleName(), timings.get().getMillis()); - } - // using side-effect free access to MM validation result - var validationResult = getMetaModelContext().getSpecificationLoader().getValidationResult() + var validationResult = specificationLoader().getValidationResult() .orElse(null); if(validationResult!=null && validationResult.hasFailures()) { @@ -295,8 +254,7 @@ public IRequestHandler onException(final RequestCycle cycle, final Exception ex) } // handle recognized exceptions gracefully also - var exceptionRecognizerService = getExceptionRecognizerService(); - var recognizedIfAny = exceptionRecognizerService.recognize(ex); + var recognizedIfAny = exceptionRecognizerService().recognize(ex); if(recognizedIfAny.isPresent()) { addWarning(recognizedIfAny.get().toMessage(getMetaModelContext().getTranslationService())); return respondGracefully(cycle); @@ -397,16 +355,13 @@ protected IRequestablePage errorPageFor(final Exception ex) { } // using side-effect free access to MM validation result - var validationResult = mmc.getSpecificationLoader().getValidationResult() + var validationResult = specificationLoader().getValidationResult() .orElse(null); if(validationResult!=null && validationResult.hasFailures()) return new MmvErrorPage(validationResult.getMessages("[%d] %s")); - var exceptionRecognizerService = mmc.getServiceRegistry() - .lookupServiceElseFail(ExceptionRecognizerService.class); - - final Optional recognition = exceptionRecognizerService + final Optional recognition = exceptionRecognizerService() .recognizeFromSelected( Can.of( pageExpiredExceptionRecognizer, @@ -461,9 +416,9 @@ private IRequestablePage newSignInPage(final ExceptionModel exceptionModel) { * Matters should improve once CAUSEWAY-299 gets implemented... */ protected boolean isSignedIn() { - if(!isInInteraction()) + if(!interactionService.isInInteraction()) return false; - return getWicketAuthenticatedWebSession().isSignedIn(); + return AuthenticatedWebSession.get().isSignedIn(); } private boolean userHasSessionWithRememberMe(final RequestCycle requestCycle) { @@ -482,18 +437,4 @@ private boolean userHasSessionWithRememberMe(final RequestCycle requestCycle) { return false; } - // -- DEPENDENCIES - - private ExceptionRecognizerService getExceptionRecognizerService() { - return getMetaModelContext().getServiceRegistry().lookupServiceElseFail(ExceptionRecognizerService.class); - } - - private boolean isInInteraction() { - return getMetaModelContext().getInteractionService().isInInteraction(); - } - - private AuthenticatedWebSession getWicketAuthenticatedWebSession() { - return AuthenticatedWebSession.get(); - } - } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java index 41bb10f8294..61d9db93014 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java @@ -21,6 +21,7 @@ import java.time.Duration; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.function.Function; @@ -41,17 +42,19 @@ import org.apache.wicket.markup.head.ResourceAggregator; import org.apache.wicket.markup.head.filter.JavaScriptFilteredIntoFooterHeaderResponse; import org.apache.wicket.markup.html.WebPage; -import org.apache.wicket.request.cycle.IRequestCycleListener; import org.apache.wicket.request.cycle.PageRequestHandlerTracker; import org.apache.wicket.request.resource.CssResourceReference; import org.apache.wicket.settings.RequestCycleSettings; import org.apache.wicket.spring.injection.annot.SpringComponentInjector; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.apache.causeway.applib.services.inject.ServiceInjector; +import org.apache.causeway.applib.services.metrics.MetricsService; import org.apache.causeway.commons.internal.concurrent._ConcurrentContext; import org.apache.causeway.commons.internal.concurrent._ConcurrentTaskList; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.config.environment.CausewaySystemEnvironment; import org.apache.causeway.core.metamodel.context.MetaModelContext; @@ -68,7 +71,10 @@ import org.apache.causeway.viewer.wicket.viewer.integration.AuthenticatedWebSessionForCauseway; import org.apache.causeway.viewer.wicket.viewer.integration.CausewayResourceSettings; import org.apache.causeway.viewer.wicket.viewer.integration.ConverterForObjectAdapter; +import org.apache.causeway.viewer.wicket.viewer.integration.RequestCycle2; import org.apache.causeway.viewer.wicket.viewer.integration.RootRequestMapper; +import org.apache.causeway.viewer.wicket.viewer.integration.TelemetryStartHandler; +import org.apache.causeway.viewer.wicket.viewer.integration.TelemetryStopHandler; import org.apache.causeway.viewer.wicket.viewer.integration.WebRequestCycleForCauseway; import lombok.Getter; @@ -112,6 +118,10 @@ public static CausewayWicketApplication get() { @Inject private List applicationInitializers; @Inject private CausewaySystemEnvironment systemEnvironment; @Inject private CausewayConfiguration configuration; + @Inject private MetricsService metricService; + + @Qualifier("causeway-wicketviewer") + @Inject private CausewayObservationInternal observationInternal; @Getter(onMethod = @__(@Override)) @Inject private ComponentFactoryRegistry componentFactoryRegistry; @@ -138,9 +148,6 @@ protected void internalInit() { // settings before any other. setResourceSettings(new CausewayResourceSettings(this)); super.internalInit(); - //setRequestCycleProvider(RequestCycle2::new); - - setRootRequestMapper(new RootRequestMapper(this)); } private AjaxRequestTarget decorate(final AjaxRequestTarget ajaxRequestTarget) { @@ -202,10 +209,15 @@ protected void init() { .submit(_ConcurrentContext.sequential()) .await(); + setRequestCycleProvider(RequestCycle2::new); + setRootRequestMapper(new RootRequestMapper(this)); getRequestCycleSettings().setRenderStrategy(RequestCycleSettings.RenderStrategy.REDIRECT_TO_RENDER); getResourceSettings().setParentFolderPlaceholder("$up$"); - getRequestCycleListeners().add(createWebRequestCycleListenerForCauseway()); + getRequestCycleListeners().add(new TelemetryStartHandler(Objects.requireNonNull(observationInternal) + .provider(TelemetryStartHandler.class))); + getRequestCycleListeners().add(new WebRequestCycleForCauseway(metaModelContext, getPageClassRegistry())); + getRequestCycleListeners().add(new TelemetryStopHandler(metricService)); getRequestCycleListeners().add(new PageRequestHandlerTracker()); //XXX CAUSEWAY-2530, don't recreate expired pages @@ -275,15 +287,6 @@ protected String defaultEncryptionKey() { // ////////////////////////////////////// - /** - * Factored out for easy (informal) pluggability. - */ - protected IRequestCycleListener createWebRequestCycleListenerForCauseway() { - var webRequestCycleForCauseway = new WebRequestCycleForCauseway(); - webRequestCycleForCauseway.setPageClassRegistry(getPageClassRegistry()); - return webRequestCycleForCauseway; - } - protected static final Function> getCssResourceReferences = (final ComponentFactory input) -> { final CssResourceReference cssResourceReference = input.getCssResourceReference(); From ef4517c3c1332fcfd507becf25def0e932331c5b Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 08:32:20 +0100 Subject: [PATCH 06/15] CAUSEWAY-3975: proper observation scope closing --- .../viewer/integration/RequestCycle2.java | 25 ++++++++++++++++--- .../integration/TelemetryStartHandler.java | 9 +++---- .../integration/TelemetryStopHandler.java | 9 +++---- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java index 7efdc44585c..bc2263624c6 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java @@ -18,8 +18,11 @@ */ package org.apache.causeway.viewer.wicket.viewer.integration; +import java.util.function.Supplier; + import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.cycle.RequestCycleContext; +import org.jspecify.annotations.Nullable; import io.micrometer.observation.Observation; import io.micrometer.observation.Observation.Scope; @@ -27,8 +30,8 @@ public class RequestCycle2 extends RequestCycle { final long startTimeNanos; - Observation observation; - Scope scope; + private Observation observation; + private Scope scope; public RequestCycle2(final RequestCycleContext context) { super(context); @@ -39,9 +42,9 @@ long millisSinceStart() { return (System.nanoTime() - startTimeNanos)/1000_000; } - void observationStartAndOpenScope() { + void observationStartAndOpenScope(final Observation observation) { if(observation==null) return; - observation.start(); + this.observation = observation.start(); this.scope = observation.openScope(); } @@ -54,4 +57,18 @@ void observationCloseScopeAndStop() { observation.stop(); } + void onObservationError(final Exception ex) { + if(observation==null) return; + observation.error(ex); + } + + void observationTag(final String key, @Nullable final Supplier valueSupplier) { + if(observation==null || valueSupplier == null) return; + try { + observation.highCardinalityKeyValue(key, "" + valueSupplier.get()); + } catch (Exception e) { + observation.highCardinalityKeyValue(key, "EXCEPTION: " + e.getMessage()); + } + } + } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java index b5eb4badd62..fdec3182d5d 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java @@ -34,16 +34,15 @@ public record TelemetryStartHandler( @Override public synchronized void onBeginRequest(final RequestCycle requestCycle) { if (requestCycle instanceof RequestCycle2 requestCycle2) { - requestCycle2.observation = observationProvider.get("Apache Wicket Request Cycle"); - requestCycle2.observationStartAndOpenScope(); + requestCycle2.observationStartAndOpenScope( + observationProvider.get("Apache Wicket Request Cycle")); } } @Override public IRequestHandler onException(final RequestCycle requestCycle, final Exception ex) { - if (requestCycle instanceof RequestCycle2 requestCycle2 - && requestCycle2.observation!=null) { - requestCycle2.observation.error(ex); + if (requestCycle instanceof RequestCycle2 requestCycle2) { + requestCycle2.onObservationError(ex); } return null; } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java index d33a11f1ff0..def31d79e24 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java @@ -32,15 +32,14 @@ public record TelemetryStopHandler( @Override public void onEndRequest(final RequestCycle requestCycle) { - if (requestCycle instanceof RequestCycle2 requestCycle2 - && requestCycle2.observation!=null) { + if (requestCycle instanceof RequestCycle2 requestCycle2) { if(requestCycle2.millisSinceStart() > 50) { // avoid clutter - requestCycle2.observation.highCardinalityKeyValue("numberEntitiesLoaded", ""+metricsService.numberEntitiesLoaded()); - requestCycle2.observation.highCardinalityKeyValue("numberEntitiesDirtied", ""+metricsService.numberEntitiesDirtied()); + requestCycle2.observationTag("numberEntitiesLoaded", metricsService::numberEntitiesLoaded); + requestCycle2.observationTag("numberEntitiesDirtied", metricsService::numberEntitiesDirtied); } - requestCycle2.observation.stop(); + requestCycle2.observationCloseScopeAndStop(); } } From e9c6bf5b81a3a693e19a03aaa9e6e52718d0ffe0 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 08:41:09 +0100 Subject: [PATCH 07/15] CAUSEWAY-3975: factors out RehydrationHandler --- .../integration/RehydrationHandler.java | 43 +++++++++++++++++++ .../viewer/integration/RootRequestMapper.java | 3 ++ .../wicketapp/CausewayWicketApplication.java | 5 ++- 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RehydrationHandler.java diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RehydrationHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RehydrationHandler.java new file mode 100644 index 00000000000..69069bf629f --- /dev/null +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RehydrationHandler.java @@ -0,0 +1,43 @@ +/* + * 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.viewer.wicket.viewer.integration; + +import org.apache.wicket.core.request.handler.ListenerRequestHandler; +import org.apache.wicket.request.IRequestHandler; +import org.apache.wicket.request.cycle.IRequestCycleListener; +import org.apache.wicket.request.cycle.RequestCycle; + +import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract; + +/** + * EXPERIMENTAL: intercept requests and reload view-models so any detached entities are re-fetched + * + * @since 2.0 (refactored for v4) + */ +public record RehydrationHandler() implements IRequestCycleListener { + + @Override + public void onRequestHandlerResolved(final RequestCycle cycle, final IRequestHandler handler) { + if (handler instanceof ListenerRequestHandler listenerRequestHandler + && listenerRequestHandler.getPage() instanceof PageAbstract pageAbstract) { + pageAbstract.onNewRequestCycle(); + } + } + +} diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java index 7e6b7cfd9c7..965c6d76b0d 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java @@ -28,13 +28,16 @@ import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract; +@Deprecated // was replaced by RehydrationHandler public final class RootRequestMapper extends SystemMapper implements IRequestMapper { + @Deprecated public RootRequestMapper(final Application application) { super(application); } // intercept AJAX requests and reload view-models so any detached entities are re-fetched + @Deprecated @Override protected IRequestMapper newPageInstanceMapper() { return new PageInstanceMapper() { diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java index 61d9db93014..1cc27a57d4b 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java @@ -71,8 +71,8 @@ import org.apache.causeway.viewer.wicket.viewer.integration.AuthenticatedWebSessionForCauseway; import org.apache.causeway.viewer.wicket.viewer.integration.CausewayResourceSettings; import org.apache.causeway.viewer.wicket.viewer.integration.ConverterForObjectAdapter; +import org.apache.causeway.viewer.wicket.viewer.integration.RehydrationHandler; import org.apache.causeway.viewer.wicket.viewer.integration.RequestCycle2; -import org.apache.causeway.viewer.wicket.viewer.integration.RootRequestMapper; import org.apache.causeway.viewer.wicket.viewer.integration.TelemetryStartHandler; import org.apache.causeway.viewer.wicket.viewer.integration.TelemetryStopHandler; import org.apache.causeway.viewer.wicket.viewer.integration.WebRequestCycleForCauseway; @@ -210,7 +210,7 @@ protected void init() { .await(); setRequestCycleProvider(RequestCycle2::new); - setRootRequestMapper(new RootRequestMapper(this)); + //setRootRequestMapper(new RootRequestMapper(this)); getRequestCycleSettings().setRenderStrategy(RequestCycleSettings.RenderStrategy.REDIRECT_TO_RENDER); getResourceSettings().setParentFolderPlaceholder("$up$"); @@ -218,6 +218,7 @@ protected void init() { .provider(TelemetryStartHandler.class))); getRequestCycleListeners().add(new WebRequestCycleForCauseway(metaModelContext, getPageClassRegistry())); getRequestCycleListeners().add(new TelemetryStopHandler(metricService)); + getRequestCycleListeners().add(new RehydrationHandler()); getRequestCycleListeners().add(new PageRequestHandlerTracker()); //XXX CAUSEWAY-2530, don't recreate expired pages From eb1f320f1e706ef019187a097d62b8879fdff84c Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 09:32:40 +0100 Subject: [PATCH 08/15] CAUSEWAY-3975: adds observation 'Causeway Layered Interaction' properly spanning till interaction closes --- .../services/iactnlayer/InteractionLayer.java | 16 +++++- .../iactnlayer/InteractionLayerStack.java | 18 +++++-- .../CausewayObservationInternal.java | 50 +++++++++++++++++++ .../session/InteractionServiceDefault.java | 25 ++++------ .../InteractionService_forTesting.java | 4 +- .../viewer/integration/RequestCycle2.java | 30 +++-------- 6 files changed, 99 insertions(+), 44 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayer.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayer.java index 13da3e007fb..c4475490ab3 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayer.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayer.java @@ -21,6 +21,7 @@ import org.jspecify.annotations.Nullable; import org.apache.causeway.applib.services.iactn.Interaction; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal.ObservationClosure; /** * Binds an {@link Interaction} ("what" is being executed) with @@ -46,7 +47,8 @@ public record InteractionLayer( * WHO is performing this {@link #getInteraction()}, also * WHEN and WHERE. */ - InteractionContext interactionContext) { + InteractionContext interactionContext, + ObservationClosure observationClosure) implements AutoCloseable { public boolean isRoot() { return parent==null; @@ -68,4 +70,16 @@ public InteractionLayer rootLayer() { : this; } + @Override + public void close() { + observationClosure.close(); + } + + public void closeAll() { + close(); + if(parent!=null) { + parent.closeAll(); + } + } + } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayerStack.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayerStack.java index 3791d22d494..9a1b7b69fdb 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayerStack.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionLayerStack.java @@ -24,6 +24,9 @@ import org.jspecify.annotations.Nullable; import org.apache.causeway.applib.services.iactn.Interaction; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal.ObservationClosure; + +import io.micrometer.observation.Observation; public final class InteractionLayerStack { @@ -38,14 +41,17 @@ public Optional currentLayer() { public InteractionLayer push( final Interaction interaction, - final InteractionContext interactionContext) { + final InteractionContext interactionContext, + final Observation observation) { var parent = currentLayer().orElse(null); - var newLayer = new InteractionLayer(parent, interaction, interactionContext); + @SuppressWarnings("resource") + var newLayer = new InteractionLayer(parent, interaction, interactionContext, new ObservationClosure().startAndOpenScope(observation)); threadLocalLayer.set(newLayer); return newLayer; } public void clear() { + currentLayer().ifPresent(InteractionLayer::closeAll); threadLocalLayer.remove(); } @@ -67,9 +73,11 @@ public InteractionLayer peek() { @Nullable public InteractionLayer pop() { var current = threadLocalLayer.get(); - return set(current != null - ? current.parent() - : null); + if(current==null) return null; + + var newTop = current.parent(); + current.close(); + return set(newTop); } public void popWhile(final Predicate condition) { diff --git a/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java b/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java index f4387ed04d9..2d7ea230a6b 100644 --- a/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java +++ b/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java @@ -19,10 +19,17 @@ package org.apache.causeway.commons.internal.observation; import java.util.Optional; +import java.util.function.Supplier; + +import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; +import lombok.Data; +import lombok.experimental.Accessors; + import io.micrometer.observation.Observation; +import io.micrometer.observation.Observation.Scope; import io.micrometer.observation.ObservationRegistry; /** @@ -73,4 +80,47 @@ public ObservationProvider provider(final Class bean) { return name->createNotStarted(bean, name); } + /** + * Helps if start and stop of an {@link Observation} happen in different code locations. + */ + @Data @Accessors(fluent = true) + public static class ObservationClosure implements AutoCloseable { + + private Observation observation; + private Scope scope; + + public ObservationClosure startAndOpenScope(final Observation observation) { + if(observation==null) return this; + this.observation = observation.start(); + this.scope = observation.openScope(); + return this; + } + + @Override + public void close() { + if(observation==null) return; + if(scope!=null) { + this.scope.close(); + this.scope = null; + } + observation.stop(); + } + + public void onError(final Exception ex) { + if(observation==null) return; + observation.error(ex); + } + + public ObservationClosure tag(final String key, @Nullable final Supplier valueSupplier) { + if(observation==null || valueSupplier == null) return this; + try { + observation.highCardinalityKeyValue(key, "" + valueSupplier.get()); + } catch (Exception e) { + observation.highCardinalityKeyValue(key, "EXCEPTION: " + e.getMessage()); + } + return this; + } + + } + } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java index 7f2f854fa20..5930eaa18a0 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java @@ -209,23 +209,20 @@ private InteractionLayer openInteraction(final @NonNull InteractionContext inter // we are done, just return the stack's top return currentInteractionLayerElseFail(); - var newInteractionLayer = observationProvider.get("New Interaction Layer") - .highCardinalityKeyValue("stackSize", ""+getInteractionLayerCount()) - .observe(()->{ + var causewayInteraction = currentInteractionLayer() + .map(InteractionLayer::interaction) + .map(it->(CausewayInteraction)it) + .orElseGet(()->new CausewayInteraction(interactionIdGenerator.interactionId())); - var causewayInteraction = currentInteractionLayer() - .map(InteractionLayer::interaction) - .map(it->(CausewayInteraction)it) - .orElseGet(()->new CausewayInteraction(interactionIdGenerator.interactionId())); - var interactionLayer = layerStack.push(causewayInteraction, interactionContextToUse); + var obs = observationProvider.get("Causeway Layered Interaction") + .highCardinalityKeyValue("stackSize", ""+getInteractionLayerCount()); - if(isAtTopLevel()) { - transactionServiceSpring.onOpen(causewayInteraction); - interactionScopeLifecycleHandler.onTopLevelInteractionOpened(); - } + var newInteractionLayer = layerStack.push(causewayInteraction, interactionContextToUse, obs); - return interactionLayer; - }); + if(isAtTopLevel()) { + transactionServiceSpring.onOpen(causewayInteraction); + interactionScopeLifecycleHandler.onTopLevelInteractionOpened(); + } if(log.isDebugEnabled()) { log.debug("new interaction layer created (interactionId={}, total-layers-on-stack={}, {})", diff --git a/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java b/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java index 95f18fcb0f2..54cf6b2baba 100644 --- a/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java +++ b/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java @@ -38,6 +38,8 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import io.micrometer.observation.Observation; + /** * A pass-through implementation, free of side-effects, * in support of simple JUnit tests. @@ -56,7 +58,7 @@ private InteractionLayer openInteraction() { // @Override private InteractionLayer openInteraction(final @NonNull InteractionContext interactionContext) { final Interaction interaction = new Interaction_forTesting(); - return layerStack.push(interaction, interactionContext); + return layerStack.push(interaction, interactionContext, Observation.NOOP); } @Override diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java index bc2263624c6..8dedb9ed985 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java @@ -24,14 +24,14 @@ import org.apache.wicket.request.cycle.RequestCycleContext; import org.jspecify.annotations.Nullable; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal.ObservationClosure; + import io.micrometer.observation.Observation; -import io.micrometer.observation.Observation.Scope; public class RequestCycle2 extends RequestCycle { final long startTimeNanos; - private Observation observation; - private Scope scope; + final ObservationClosure observationClosure = new ObservationClosure(); public RequestCycle2(final RequestCycleContext context) { super(context); @@ -43,32 +43,16 @@ long millisSinceStart() { } void observationStartAndOpenScope(final Observation observation) { - if(observation==null) return; - this.observation = observation.start(); - this.scope = observation.openScope(); + observationClosure.startAndOpenScope(observation); } - void observationCloseScopeAndStop() { - if(observation==null) return; - if(scope!=null) { - this.scope.close(); - this.scope = null; - } - observation.stop(); + observationClosure.close(); } - void onObservationError(final Exception ex) { - if(observation==null) return; - observation.error(ex); + observationClosure.onError(ex); } - void observationTag(final String key, @Nullable final Supplier valueSupplier) { - if(observation==null || valueSupplier == null) return; - try { - observation.highCardinalityKeyValue(key, "" + valueSupplier.get()); - } catch (Exception e) { - observation.highCardinalityKeyValue(key, "EXCEPTION: " + e.getMessage()); - } + observationClosure.tag(key, valueSupplier); } } From 799cd43beaf38cdd6296734f73ad76a9f167b2c0 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 09:41:26 +0100 Subject: [PATCH 09/15] CAUSEWAY-3975: cleaning up --- .../conf/Configuration_usingWicket.java | 4 +- .../viewer/integration/RequestCycle2.java | 20 +------ .../viewer/integration/RootRequestMapper.java | 56 ------------------- .../integration/TelemetryStartHandler.java | 4 +- .../integration/TelemetryStopHandler.java | 6 +- .../wicketapp/CausewayWicketApplication.java | 1 - 6 files changed, 8 insertions(+), 83 deletions(-) delete mode 100644 viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java diff --git a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java index e8c488746d9..c01c6cf8cc4 100644 --- a/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java +++ b/regressiontests/base/src/main/java/org/apache/causeway/testdomain/conf/Configuration_usingWicket.java @@ -61,7 +61,7 @@ import org.apache.causeway.viewer.wicket.ui.pages.PageClassRegistry; import org.apache.causeway.viewer.wicket.ui.pages.obj.DomainObjectPage; import org.apache.causeway.viewer.wicket.viewer.CausewayModuleViewerWicketViewer; -import org.apache.causeway.viewer.wicket.viewer.integration.RootRequestMapper; +import org.apache.causeway.viewer.wicket.viewer.integration.RehydrationHandler; import lombok.AccessLevel; import lombok.Getter; @@ -349,7 +349,7 @@ protected IPageFactory newPageFactory() { @Override protected void internalInit() { super.internalInit(); - setRootRequestMapper(new RootRequestMapper(this)); + getRequestCycleListeners().add(new RehydrationHandler()); } } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java index 8dedb9ed985..7bda8be00ba 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RequestCycle2.java @@ -18,20 +18,15 @@ */ package org.apache.causeway.viewer.wicket.viewer.integration; -import java.util.function.Supplier; - import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.cycle.RequestCycleContext; -import org.jspecify.annotations.Nullable; import org.apache.causeway.commons.internal.observation.CausewayObservationInternal.ObservationClosure; -import io.micrometer.observation.Observation; - public class RequestCycle2 extends RequestCycle { final long startTimeNanos; - final ObservationClosure observationClosure = new ObservationClosure(); + public final ObservationClosure observationClosure = new ObservationClosure(); public RequestCycle2(final RequestCycleContext context) { super(context); @@ -42,17 +37,4 @@ long millisSinceStart() { return (System.nanoTime() - startTimeNanos)/1000_000; } - void observationStartAndOpenScope(final Observation observation) { - observationClosure.startAndOpenScope(observation); - } - void observationCloseScopeAndStop() { - observationClosure.close(); - } - void onObservationError(final Exception ex) { - observationClosure.onError(ex); - } - void observationTag(final String key, @Nullable final Supplier valueSupplier) { - observationClosure.tag(key, valueSupplier); - } - } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java deleted file mode 100644 index 965c6d76b0d..00000000000 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/RootRequestMapper.java +++ /dev/null @@ -1,56 +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.viewer.wicket.viewer.integration; - -import org.apache.wicket.Application; -import org.apache.wicket.SystemMapper; -import org.apache.wicket.core.request.handler.ListenerRequestHandler; -import org.apache.wicket.core.request.mapper.PageInstanceMapper; -import org.apache.wicket.request.IRequestHandler; -import org.apache.wicket.request.IRequestMapper; -import org.apache.wicket.request.Request; - -import org.apache.causeway.viewer.wicket.ui.pages.PageAbstract; - -@Deprecated // was replaced by RehydrationHandler -public final class RootRequestMapper extends SystemMapper implements IRequestMapper { - - @Deprecated - public RootRequestMapper(final Application application) { - super(application); - } - - // intercept AJAX requests and reload view-models so any detached entities are re-fetched - @Deprecated - @Override - protected IRequestMapper newPageInstanceMapper() { - return new PageInstanceMapper() { - @Override - public IRequestHandler mapRequest(final Request request) { - var handler = super.mapRequest(request); - if (handler instanceof ListenerRequestHandler listenerRequestHandler - && listenerRequestHandler.getPage() instanceof PageAbstract pageAbstract) { - pageAbstract.onNewRequestCycle(); - } - return handler; - } - }; - } - -} diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java index fdec3182d5d..daeef157a43 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java @@ -34,7 +34,7 @@ public record TelemetryStartHandler( @Override public synchronized void onBeginRequest(final RequestCycle requestCycle) { if (requestCycle instanceof RequestCycle2 requestCycle2) { - requestCycle2.observationStartAndOpenScope( + requestCycle2.observationClosure.startAndOpenScope( observationProvider.get("Apache Wicket Request Cycle")); } } @@ -42,7 +42,7 @@ public synchronized void onBeginRequest(final RequestCycle requestCycle) { @Override public IRequestHandler onException(final RequestCycle requestCycle, final Exception ex) { if (requestCycle instanceof RequestCycle2 requestCycle2) { - requestCycle2.onObservationError(ex); + requestCycle2.observationClosure.onError(ex); } return null; } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java index def31d79e24..646bad2c5b3 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStopHandler.java @@ -35,11 +35,11 @@ public void onEndRequest(final RequestCycle requestCycle) { if (requestCycle instanceof RequestCycle2 requestCycle2) { if(requestCycle2.millisSinceStart() > 50) { // avoid clutter - requestCycle2.observationTag("numberEntitiesLoaded", metricsService::numberEntitiesLoaded); - requestCycle2.observationTag("numberEntitiesDirtied", metricsService::numberEntitiesDirtied); + requestCycle2.observationClosure.tag("numberEntitiesLoaded", metricsService::numberEntitiesLoaded); + requestCycle2.observationClosure.tag("numberEntitiesDirtied", metricsService::numberEntitiesDirtied); } - requestCycle2.observationCloseScopeAndStop(); + requestCycle2.observationClosure.close(); } } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java index 1cc27a57d4b..597e9548e82 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java @@ -210,7 +210,6 @@ protected void init() { .await(); setRequestCycleProvider(RequestCycle2::new); - //setRootRequestMapper(new RootRequestMapper(this)); getRequestCycleSettings().setRenderStrategy(RequestCycleSettings.RenderStrategy.REDIRECT_TO_RENDER); getResourceSettings().setParentFolderPlaceholder("$up$"); From 804b43c558fc42d59a421e93ea9d6bee158e0e48 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 10:07:50 +0100 Subject: [PATCH 10/15] CAUSEWAY-3975: separation of concerns (mm init) --- .../CausewayModuleCoreMetamodel.java | 6 +- .../services/init/MetamodelInitializer.java | 108 ++++++++++++++++++ .../session/InteractionServiceDefault.java | 67 +---------- 3 files changed, 114 insertions(+), 67 deletions(-) create mode 100644 core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/init/MetamodelInitializer.java diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java index 4f6b1a6b210..4b251c0e9cc 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java @@ -67,6 +67,7 @@ import org.apache.causeway.core.metamodel.services.grid.GridServiceDefault; import org.apache.causeway.core.metamodel.services.grid.spi.LayoutResourceLoaderDefault; import org.apache.causeway.core.metamodel.services.idstringifier.IdStringifierLookupService; +import org.apache.causeway.core.metamodel.services.init.MetamodelInitializer; import org.apache.causeway.core.metamodel.services.inject.ServiceInjectorDefault; import org.apache.causeway.core.metamodel.services.layout.LayoutServiceDefault; import org.apache.causeway.core.metamodel.services.metamodel.MetaModelServiceDefault; @@ -206,7 +207,10 @@ LogicalTypeMalformedValidator.class, // menubar contributions - MetamodelInspectMenu.class + MetamodelInspectMenu.class, + + //last + MetamodelInitializer.class }) public class CausewayModuleCoreMetamodel { diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/init/MetamodelInitializer.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/init/MetamodelInitializer.java new file mode 100644 index 00000000000..c9d817a96cf --- /dev/null +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/init/MetamodelInitializer.java @@ -0,0 +1,108 @@ +/* + * 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.services.init; + +import java.io.File; + +import jakarta.inject.Inject; +import jakarta.inject.Provider; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import org.apache.causeway.applib.events.metamodel.MetamodelEvent; +import org.apache.causeway.applib.services.eventbus.EventBusService; +import org.apache.causeway.applib.util.schema.ChangesDtoUtils; +import org.apache.causeway.applib.util.schema.CommandDtoUtils; +import org.apache.causeway.applib.util.schema.InteractionDtoUtils; +import org.apache.causeway.applib.util.schema.InteractionsDtoUtils; +import org.apache.causeway.commons.internal.concurrent._ConcurrentContext; +import org.apache.causeway.commons.internal.concurrent._ConcurrentTaskList; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal.ObservationProvider; +import org.apache.causeway.core.metamodel.specloader.SpecificationLoader; + +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +public record MetamodelInitializer( + EventBusService eventBusService, + Provider specificationLoaderProvider, + ObservationProvider observationProvider) { + + @Inject + public MetamodelInitializer( + final EventBusService eventBusService, + final Provider specificationLoaderProvider, + @Qualifier("causeway-metamodel") + final CausewayObservationInternal observation) { + this(eventBusService, specificationLoaderProvider, observation.provider(MetamodelInitializer.class)); + } + + @EventListener + public void init(final ContextRefreshedEvent event) { + log.info("Initialising Causeway System"); + log.info("working directory: {}", new File(".").getAbsolutePath()); + + observationProvider.get("Initialising Causeway System").observe(() -> { + observationProvider.get("Notify BEFORE_METAMODEL_LOADING Listeners").observe(() -> { + eventBusService.post(MetamodelEvent.BEFORE_METAMODEL_LOADING); + }); + + observationProvider.get("Initialising Causeway Metamodel").observe(() -> { + initMetamodel(specificationLoaderProvider.get()); + }); + + observationProvider.get("Notify AFTER_METAMODEL_LOADED Listeners").observe(() -> { + eventBusService.post(MetamodelEvent.AFTER_METAMODEL_LOADED); + }); + }); + } + + private void initMetamodel(final SpecificationLoader specificationLoader) { + + var taskList = _ConcurrentTaskList.named("CausewayInteractionFactoryDefault Init") + .addRunnable("SpecificationLoader::createMetaModel", specificationLoader::createMetaModel) + .addRunnable("ChangesDtoUtils::init", ChangesDtoUtils::init) + .addRunnable("InteractionDtoUtils::init", InteractionDtoUtils::init) + .addRunnable("InteractionsDtoUtils::init", InteractionsDtoUtils::init) + .addRunnable("CommandDtoUtils::init", CommandDtoUtils::init) + ; + + taskList.submit(_ConcurrentContext.forkJoin()); + taskList.await(); + + { // log any validation failures, experimental code however, not sure how to best propagate failures + var validationResult = specificationLoader.getOrAssessValidationResult(); + if(validationResult.getNumberOfFailures()==0) { + log.info("Validation PASSED"); + } else { + log.error("### Validation FAILED, failure count: {}", validationResult.getNumberOfFailures()); + validationResult.forEach(failure->{ + log.error("# " + failure.message()); + }); + //throw _Exceptions.unrecoverable("Validation FAILED"); + } + } + } + +} diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java index 5930eaa18a0..61738dd9d12 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java @@ -18,7 +18,6 @@ */ package org.apache.causeway.core.runtimeservices.session; -import java.io.File; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -34,13 +33,10 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.apache.causeway.applib.annotation.PriorityPrecedence; import org.apache.causeway.applib.annotation.Programmatic; -import org.apache.causeway.applib.events.metamodel.MetamodelEvent; import org.apache.causeway.applib.services.clock.ClockService; import org.apache.causeway.applib.services.command.Command; import org.apache.causeway.applib.services.eventbus.EventBusService; @@ -51,13 +47,7 @@ import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker; import org.apache.causeway.applib.services.iactnlayer.InteractionService; import org.apache.causeway.applib.services.inject.ServiceInjector; -import org.apache.causeway.applib.util.schema.ChangesDtoUtils; -import org.apache.causeway.applib.util.schema.CommandDtoUtils; -import org.apache.causeway.applib.util.schema.InteractionDtoUtils; -import org.apache.causeway.applib.util.schema.InteractionsDtoUtils; import org.apache.causeway.commons.functional.ThrowingRunnable; -import org.apache.causeway.commons.internal.concurrent._ConcurrentContext; -import org.apache.causeway.commons.internal.concurrent._ConcurrentTaskList; import org.apache.causeway.commons.internal.debug._Probe; import org.apache.causeway.commons.internal.debug.xray.XrayUi; import org.apache.causeway.commons.internal.exceptions._Exceptions; @@ -97,9 +87,7 @@ public class InteractionServiceDefault final InteractionLayerStack layerStack = new InteractionLayerStack(); - final EventBusService eventBusService; final ObservationProvider observationProvider; - final Provider specificationLoaderProvider; final ServiceInjector serviceInjector; final ClockService clockService; @@ -111,6 +99,7 @@ public class InteractionServiceDefault final InteractionIdGenerator interactionIdGenerator; + @SuppressWarnings("exports") @Inject public InteractionServiceDefault( final EventBusService eventBusService, @@ -123,9 +112,7 @@ public InteractionServiceDefault( final Provider commandPublisherProvider, final ConfigurableBeanFactory beanFactory, final InteractionIdGenerator interactionIdGenerator) { - this.eventBusService = eventBusService; this.observationProvider = observation.provider(getClass()); - this.specificationLoaderProvider = specificationLoaderProvider; this.serviceInjector = serviceInjector; this.transactionServiceSpring = transactionServiceSpring; this.clockService = clockService; @@ -135,58 +122,6 @@ public InteractionServiceDefault( this.interactionScopeLifecycleHandler = InteractionScopeBeanFactoryPostProcessor.lookupScope(beanFactory); } - @EventListener - public void init(final ContextRefreshedEvent event) { - log.info("Initialising Causeway System"); - log.info("working directory: {}", new File(".").getAbsolutePath()); - - observationProvider.get("Initialising Causeway System") - .observe(()->{ - observationProvider.get("Notify BEFORE_METAMODEL_LOADING Listeners") - .observe(()->{ - eventBusService.post(MetamodelEvent.BEFORE_METAMODEL_LOADING); - }); - - observationProvider.get("Initialising Causeway Metamodel") - .observe(()->{ - initMetamodel(specificationLoaderProvider.get()); - }); - - observationProvider.get("Notify AFTER_METAMODEL_LOADED Listeners") - .observe(()->{ - eventBusService.post(MetamodelEvent.AFTER_METAMODEL_LOADED); - }); - }); - } - - //TODO this is a metamodel concern, why is it in runtime services? - private void initMetamodel(final SpecificationLoader specificationLoader) { - - var taskList = _ConcurrentTaskList.named("CausewayInteractionFactoryDefault Init") - .addRunnable("SpecificationLoader::createMetaModel", specificationLoader::createMetaModel) - .addRunnable("ChangesDtoUtils::init", ChangesDtoUtils::init) - .addRunnable("InteractionDtoUtils::init", InteractionDtoUtils::init) - .addRunnable("InteractionsDtoUtils::init", InteractionsDtoUtils::init) - .addRunnable("CommandDtoUtils::init", CommandDtoUtils::init) - ; - - taskList.submit(_ConcurrentContext.forkJoin()); - taskList.await(); - - { // log any validation failures, experimental code however, not sure how to best propagate failures - var validationResult = specificationLoader.getOrAssessValidationResult(); - if(validationResult.getNumberOfFailures()==0) { - log.info("Validation PASSED"); - } else { - log.error("### Validation FAILED, failure count: {}", validationResult.getNumberOfFailures()); - validationResult.forEach(failure->{ - log.error("# " + failure.message()); - }); - //throw _Exceptions.unrecoverable("Validation FAILED"); - } - } - } - @Override public int getInteractionLayerCount() { return layerStack.size(); From 1f6da31c7a56ed9069cfbe9d9380f9217834029b Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 10:18:33 +0100 Subject: [PATCH 11/15] CAUSEWAY-3975: revert intro of TestSupport (1) --- .../iactnlayer/InteractionService.java | 70 ++++++++++--------- .../session/InteractionServiceDefault.java | 14 ++-- .../InteractionService_forTesting.java | 8 +-- .../CommandLog_IntegTestAbstract.java | 2 +- .../ExecutionLog_IntegTestAbstract.java | 2 +- .../ExecutionOutbox_IntegTestAbstract.java | 18 ++--- .../applib/CausewayInteractionHandler.java | 8 +-- .../applib/NoPermissionChecks.java | 2 +- .../applib/UserMementoRefiners.java | 2 +- .../WebRequestCycleForCauseway.java | 2 +- 10 files changed, 62 insertions(+), 66 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java index 60845096603..ea7a306bd12 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java @@ -56,6 +56,42 @@ */ public interface InteractionService extends InteractionLayerTracker { + /** + * If present, reuses the current top level {@link InteractionLayer}, otherwise creates a new + * anonymous one. + * + * @see #openInteraction(InteractionContext) + */ + InteractionLayer openInteraction(); + + /** + * Returns a new or reused {@link InteractionLayer} that is a holder of the {@link InteractionContext} + * on top of the current thread's interaction layer stack. + * + *

+ * If available reuses an existing {@link InteractionContext}, otherwise creates a new one. + *

+ * + *

+ * The {@link InteractionLayer} represents a user's span of activities interacting with + * the application. These can be stacked (usually temporarily), for example for a sudo + * session or to mock the clock. The stack is later closed using {@link #closeInteractionLayers()}. + *

+ * + * @param interactionContext + * + * @apiNote if the current {@link InteractionLayer} (if any) has an {@link InteractionContext} that + * equals that of the given one, as an optimization, no new layer is pushed onto the stack; + * instead the current one is returned + */ + InteractionLayer openInteraction( + @NonNull InteractionContext interactionContext); + + /** + * closes all open {@link InteractionLayer}(s) as stacked on the current thread + */ + void closeInteractionLayers(); + /** * @return whether the calling thread is within the context of an open {@link InteractionLayer} */ @@ -169,11 +205,6 @@ default Try runAnonymousAndCatch( return callAnonymousAndCatch(runnable.toCallable()); } - /** - * closes all open {@link InteractionLayer}(s) as stacked on the current thread - */ - void closeInteractionLayers(); - public interface TestSupport { T model(); /** @@ -198,35 +229,6 @@ public interface TestSupport { * @see #nextInteraction() */ void nextInteraction(final InteractionContext interactionContext, final Consumer callback); - /** - * If present, reuses the current top level {@link InteractionLayer}, otherwise creates a new - * anonymous one. - * - * @see #openInteraction(InteractionContext) - */ - InteractionLayer openInteraction(); - /** - * Returns a new or reused {@link InteractionLayer} that is a holder of the {@link InteractionContext} - * on top of the current thread's interaction layer stack. - * - *

- * If available reuses an existing {@link InteractionContext}, otherwise creates a new one. - *

- * - *

- * The {@link InteractionLayer} represents a user's span of activities interacting with - * the application. These can be stacked (usually temporarily), for example for a sudo - * session or to mock the clock. The stack is later closed using {@link #closeInteractionLayers()}. - *

- * - * @param interactionContext - * - * @apiNote if the current {@link InteractionLayer} (if any) has an {@link InteractionContext} that - * equals that of the given one, as an optimization, no new layer is pushed onto the stack; - * instead the current one is returned - */ - InteractionLayer openInteraction( - @NonNull InteractionContext interactionContext); } TestSupport testSupport(T model); diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java index 61738dd9d12..45e0c081f60 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java @@ -127,13 +127,15 @@ public int getInteractionLayerCount() { return layerStack.size(); } - private InteractionLayer openInteraction() { + @Override + public InteractionLayer openInteraction() { return currentInteractionLayer() // or else create an anonymous authentication layer .orElseGet(()->openInteraction(InteractionContextFactory.anonymous())); } - private InteractionLayer openInteraction(final @NonNull InteractionContext interactionContextToUse) { + @Override + public InteractionLayer openInteraction(final @NonNull InteractionContext interactionContextToUse) { // check whether we should reuse any current interactionLayer, // that is, if current authentication and authToUse are equal @@ -465,14 +467,6 @@ public void nextInteraction(final InteractionContext interactionContext, final C interactionService.openInteraction(interactionContext); callback.accept(model); } - @Override - public InteractionLayer openInteraction() { - return interactionService.openInteraction(); - } - @Override - public InteractionLayer openInteraction(@NonNull final InteractionContext interactionContext) { - return interactionService.openInteraction(interactionContext); - } } @Override diff --git a/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java b/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java index 54cf6b2baba..c71da8c8faa 100644 --- a/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java +++ b/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java @@ -49,14 +49,14 @@ public class InteractionService_forTesting final InteractionLayerStack layerStack = new InteractionLayerStack(); -// @Override - private InteractionLayer openInteraction() { + @Override + public InteractionLayer openInteraction() { final UserMemento userMemento = UserMemento.system(); return openInteraction(InteractionContext.ofUserWithSystemDefaults(userMemento)); } -// @Override - private InteractionLayer openInteraction(final @NonNull InteractionContext interactionContext) { + @Override + public InteractionLayer openInteraction(final @NonNull InteractionContext interactionContext) { final Interaction interaction = new Interaction_forTesting(); return layerStack.push(interaction, interactionContext, Observation.NOOP); } diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java index 8d29e0e4052..8057f15c7ee 100644 --- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java +++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java @@ -345,7 +345,7 @@ class Model { } ); }); - testSupport.openInteraction(); + interactionService.openInteraction(); // given (same user, different target, same day) counter2 = counterRepository.findByName("counter-2"); diff --git a/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java b/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java index 5558698bd76..77e4b7c623a 100644 --- a/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java +++ b/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java @@ -310,7 +310,7 @@ class Model { }); }); - testSupport.openInteraction(); + interactionService.openInteraction(); // given (same user, different target, same day) counter2 = counterRepository.findByName("counter-2"); diff --git a/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java b/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java index c98204dd148..9455cc3d7a9 100644 --- a/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java +++ b/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java @@ -89,7 +89,7 @@ void invoke_mixin() { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); interactionService.closeInteractionLayers(); // to flush - testSupport.openInteraction(); + interactionService.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -120,7 +120,7 @@ void invoke_direct() { wrapperFactory.wrap(counter1).bumpUsingDeclaredAction(); interactionService.closeInteractionLayers(); // to flush - testSupport.openInteraction(); + interactionService.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -151,7 +151,7 @@ void invoke_mixin_disabled() { wrapperFactory.wrapMixin(Counter_bumpUsingMixinWithExecutionPublishingDisabled.class, counter1).act(); interactionService.closeInteractionLayers(); // to flush - testSupport.openInteraction(); + interactionService.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -165,7 +165,7 @@ void invoke_direct_disabled() { wrapperFactory.wrap(counter1).bumpUsingDeclaredActionWithExecutionPublishingDisabled(); interactionService.closeInteractionLayers(); // to flush - testSupport.openInteraction(); + interactionService.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -179,7 +179,7 @@ void edit() { wrapperFactory.wrap(counter1).setNum(99L); interactionService.closeInteractionLayers(); // to flush - testSupport.openInteraction(); + interactionService.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -209,7 +209,7 @@ void edit_disabled() { wrapperFactory.wrap(counter1).setNum2(99L); interactionService.closeInteractionLayers(); // to flush - testSupport.openInteraction(); + interactionService.openInteraction(); // then List all = executionOutboxEntryRepository.findOldest(); @@ -223,7 +223,7 @@ void roundtrip_EOE_bookmarks() { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); interactionService.closeInteractionLayers(); // to flush - testSupport.openInteraction(); + interactionService.openInteraction(); List all = executionOutboxEntryRepository.findOldest(); ExecutionOutboxEntry executionLogEntry = all.get(0); @@ -241,7 +241,7 @@ void roundtrip_EOE_bookmarks() { // when we start a new session and lookup from the bookmark interactionService.closeInteractionLayers(); - testSupport.openInteraction(); + interactionService.openInteraction(); Optional cle2IfAny = bookmarkService.lookup(eleBookmarkIfAny.get()); assertThat(cle2IfAny).isPresent(); @@ -260,7 +260,7 @@ void test_all_the_repository_methods() { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); }); interactionService.closeInteractionLayers(); // to flush - testSupport.openInteraction(); + interactionService.openInteraction(); // when List executionTarget1User1IfAny = executionOutboxEntryRepository.findOldest(); diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/CausewayInteractionHandler.java b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/CausewayInteractionHandler.java index 51f4fbea87b..cd1a1ea1c66 100644 --- a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/CausewayInteractionHandler.java +++ b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/CausewayInteractionHandler.java @@ -47,12 +47,12 @@ public void beforeEach(final ExtensionContext extensionContext) throws Exception _Helper .getInteractionFactory(extensionContext) - .map(InteractionService::testSupport) - .ifPresent(testSupport-> + .ifPresent(interactionService-> _Helper .getCustomInteractionContext(extensionContext) - .map(testSupport::openInteraction) - .orElseGet(testSupport::openInteraction)); + .ifPresentOrElse( + interactionService::openInteraction, + interactionService::openInteraction)); } @Override diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/NoPermissionChecks.java b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/NoPermissionChecks.java index ba0ff65fbf9..00a9d0dec0d 100644 --- a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/NoPermissionChecks.java +++ b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/NoPermissionChecks.java @@ -49,7 +49,7 @@ public void beforeEach(final ExtensionContext extensionContext) { interactionService.currentInteractionContext().ifPresent( currentInteractionContext -> { var sudoUser = currentInteractionContext.getUser().withRoleAdded(SudoService.ACCESS_ALL_ROLE.name()); - interactionService.testSupport().openInteraction(currentInteractionContext.withUser(sudoUser)); + interactionService.openInteraction(currentInteractionContext.withUser(sudoUser)); } ) ); diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/UserMementoRefiners.java b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/UserMementoRefiners.java index 1a97dbb1774..59237a96771 100644 --- a/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/UserMementoRefiners.java +++ b/testing/integtestsupport/applib/src/main/java/org/apache/causeway/testing/integtestsupport/applib/UserMementoRefiners.java @@ -53,7 +53,7 @@ public void beforeEach(final ExtensionContext extensionContext) { for (UserMementoRefiner userMementoRefiner : serviceRegistry.select(UserMementoRefiner.class)) { user = userMementoRefiner.refine(user); } - interactionService.testSupport().openInteraction(currentInteractionContext.withUser(user)); + interactionService.openInteraction(currentInteractionContext.withUser(user)); } ) ) diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java index b2f1650080e..d38b0743f6b 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/WebRequestCycleForCauseway.java @@ -146,7 +146,7 @@ public void onBeginRequest(final RequestCycle requestCycle) { } sessionAuthenticator.determineInteractionContext() - .ifPresent(interactionService.testSupport()::openInteraction); + .ifPresent(interactionService::openInteraction); } @Override From 00653f5e80268720da54537d07edf2b6cfd52a5d Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sat, 21 Mar 2026 10:25:19 +0100 Subject: [PATCH 12/15] CAUSEWAY-3975: revert intro of TestSupport (2) --- .../iactnlayer/InteractionService.java | 55 +- .../session/InteractionServiceDefault.java | 28 - .../InteractionService_forTesting.java | 5 - .../BackgroundService_IntegTestAbstract.java | 47 +- .../CommandLog_IntegTestAbstract.java | 598 +++++++++--------- .../ExecutionLog_IntegTestAbstract.java | 233 ++++--- .../ExecutionOutbox_IntegTestAbstract.java | 3 - .../AuditTrail_IntegTestAbstract.java | 246 ++++--- ...CmdExecAuditSessLog_IntegTestAbstract.java | 208 +++--- .../integtest/Layout_Counter_IntegTest.java | 14 +- 10 files changed, 660 insertions(+), 777 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java index ea7a306bd12..8778a90480e 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/services/iactnlayer/InteractionService.java @@ -19,7 +19,6 @@ package org.apache.causeway.applib.services.iactnlayer; import java.util.concurrent.Callable; -import java.util.function.Consumer; import org.jspecify.annotations.NonNull; @@ -205,36 +204,34 @@ default Try runAnonymousAndCatch( return callAnonymousAndCatch(runnable.toCallable()); } - public interface TestSupport { - T model(); - /** - * Primarily for testing, closes the current interaction and opens a new one. - * - *

- * In tests, this is a good way to simulate multiple interactions within a scenario. If you use the popular - * given/when/then structure, consider using at the end of each "given" or "when" block. - *

- * - * @see #closeInteractionLayers() - * @see #openInteraction() - * @see #nextInteraction(InteractionContext) - */ - void nextInteraction(final Consumer callback); - /** - * Primarily for testing, closes the current interaction and opens a new one with the specified - * {@link InteractionContext}. - * - * @see #closeInteractionLayers() - * @see #openInteraction(InteractionContext) - * @see #nextInteraction() - */ - void nextInteraction(final InteractionContext interactionContext, final Consumer callback); + /** + * Primarily for testing, closes the current interaction and opens a new one. + * + *

+ * In tests, this is a good way to simulate multiple interactions within a scenario. If you use the popular + * given/when/then structure, consider using at the end of each "given" or "when" block. + *

+ * + * @see #closeInteractionLayers() + * @see #openInteraction() + * @see #nextInteraction(InteractionContext) + */ + default InteractionLayer nextInteraction() { + closeInteractionLayers(); + return openInteraction(); } - TestSupport testSupport(T model); - - default TestSupport testSupport() { - return testSupport(null); + /** + * Primarily for testing, closes the current interaction and opens a new one with the specified + * {@link InteractionContext}. + * + * @see #closeInteractionLayers() + * @see #openInteraction(InteractionContext) + * @see #nextInteraction() + */ + default InteractionLayer nextInteraction(final InteractionContext interactionContext) { + closeInteractionLayers(); + return openInteraction(interactionContext); } } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java index 45e0c081f60..841f5f582db 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java @@ -22,7 +22,6 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.Callable; -import java.util.function.Consumer; import jakarta.annotation.Priority; import jakarta.inject.Inject; @@ -62,10 +61,7 @@ import org.apache.causeway.core.runtimeservices.transaction.TransactionServiceSpring; import org.apache.causeway.core.security.authentication.InteractionContextFactory; -import lombok.Getter; import lombok.SneakyThrows; -import lombok.Value; -import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; /** @@ -450,28 +446,4 @@ public void completeAndPublishCurrentCommand() { interaction.clear(); } - @Value - static final class TestSupportImpl implements TestSupport { - @Getter @Accessors(fluent = true) final T model; - final InteractionServiceDefault interactionService; - - @Override - public void nextInteraction(final Consumer callback) { - interactionService.closeInteractionLayers(); - interactionService.openInteraction(); - callback.accept(model); - } - @Override - public void nextInteraction(final InteractionContext interactionContext, final Consumer callback) { - interactionService.closeInteractionLayers(); - interactionService.openInteraction(interactionContext); - callback.accept(model); - } - } - - @Override - public TestSupport testSupport(final T model) { - return new TestSupportImpl<>(model, this); - } - } diff --git a/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java b/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java index c71da8c8faa..18c0d0909d9 100644 --- a/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java +++ b/core/security/src/main/java/org/apache/causeway/core/security/_testing/InteractionService_forTesting.java @@ -140,9 +140,4 @@ static class Interaction_forTesting implements Interaction { @Override public Execution getPriorExecution() { return null; } } - @Override - public TestSupport testSupport(final T model) { - throw new UnsupportedOperationException(); - } - } 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 49124ebbc3a..cb09a0b956a 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 @@ -38,7 +38,6 @@ import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.wrapper.WrapperFactory; import org.apache.causeway.applib.services.wrapper.WrapperFactory.AsyncProxy; import org.apache.causeway.applib.services.xactn.TransactionService; @@ -70,7 +69,6 @@ public abstract class BackgroundService_IntegTestAbstract extends CausewayIntegr JobExecutionContext mockQuartzJobExecutionContext = Mockito.mock(JobExecutionContext.class); Bookmark bookmark; - private TestSupport testSupport; protected abstract T newCounter(String name); @@ -89,7 +87,6 @@ static void reset_environment() { @BeforeEach void setup_counter() { - this.testSupport = interactionService.testSupport(); transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { counterRepository.removeAll(); @@ -214,29 +211,27 @@ void using_background_service() { // when (simulate quartz running in the background) runBackgroundCommandsJob.execute(mockQuartzJobExecutionContext); - testSupport.nextInteraction(ia->{ - - // then bumped - transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { - var counter = bookmarkService.lookup(bookmark, Counter.class).orElseThrow(); - assertThat(counter.getNum()).isEqualTo(1L); - }).ifFailureFail(); - - // and marked as started and completed - transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { - var after = commandLogEntryRepository.findAll(); - assertThat(after).hasSize(1); - CommandLogEntry commandLogEntryAfter = after.get(0); - - assertThat(commandLogEntryAfter) - .satisfies(x -> assertThat(x.getStartedAt()).isNotNull()) // changed - .satisfies(x -> assertThat(x.getCompletedAt()).isNotNull()) // changed - .satisfies(x -> assertThat(x.getResult()).isNotNull()) // changed - .satisfies(x -> assertThat(x.getResultSummary()).isNotNull()) // changed - ; - }).ifFailureFail(); - - }); + interactionService.nextInteraction(); + + // then bumped + transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { + var counter = bookmarkService.lookup(bookmark, Counter.class).orElseThrow(); + assertThat(counter.getNum()).isEqualTo(1L); + }).ifFailureFail(); + + // and marked as started and completed + transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> { + var after = commandLogEntryRepository.findAll(); + assertThat(after).hasSize(1); + CommandLogEntry commandLogEntryAfter = after.get(0); + + assertThat(commandLogEntryAfter) + .satisfies(x -> assertThat(x.getStartedAt()).isNotNull()) // changed + .satisfies(x -> assertThat(x.getCompletedAt()).isNotNull()) // changed + .satisfies(x -> assertThat(x.getResult()).isNotNull()) // changed + .satisfies(x -> assertThat(x.getResultSummary()).isNotNull()) // changed + ; + }).ifFailureFail(); } diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java index 8057f15c7ee..2a5d2951337 100644 --- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java +++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java @@ -39,7 +39,6 @@ import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker; import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.sudo.SudoService; import org.apache.causeway.applib.services.user.UserMemento; import org.apache.causeway.applib.services.wrapper.WrapperFactory; @@ -59,17 +58,6 @@ public abstract class CommandLog_IntegTestAbstract extends CausewayIntegrationTestAbstract { - @Inject CommandLogEntryRepository commandLogEntryRepository; - @Inject SudoService sudoService; - @Inject ClockService clockService; - @Inject InteractionService interactionService; - @Inject InteractionLayerTracker interactionLayerTracker; - @Inject CounterRepository counterRepository; - @Inject WrapperFactory wrapperFactory; - @Inject BookmarkService bookmarkService; - @Inject CausewayBeanTypeRegistry causewayBeanTypeRegistry; - - @BeforeAll static void beforeAll() { CausewayPresets.forcePrototyping(); @@ -77,25 +65,23 @@ static void beforeAll() { Counter counter1; Counter counter2; - private TestSupport testSupport; @BeforeEach void beforeEach() { - this.testSupport = interactionService.testSupport(); - testSupport.nextInteraction(ia->{ - counterRepository.removeAll(); - commandLogEntryRepository.removeAll(); + interactionService.nextInteraction(); - assertThat(counterRepository.find()).isEmpty(); + counterRepository.removeAll(); + commandLogEntryRepository.removeAll(); - counter1 = counterRepository.persist(newCounter("counter-1")); - counter2 = counterRepository.persist(newCounter("counter-2")); + assertThat(counterRepository.find()).isEmpty(); - assertThat(counterRepository.find()).hasSize(2); + counter1 = counterRepository.persist(newCounter("counter-1")); + counter2 = counterRepository.persist(newCounter("counter-2")); - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isEmpty(); - }); + assertThat(counterRepository.find()).hasSize(2); + + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isEmpty(); } protected abstract T newCounter(String name); @@ -105,34 +91,33 @@ void invoke_mixin() { // when wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - testSupport.nextInteraction(ia->{ - - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isPresent(); - - CommandLogEntry commandLogEntry = mostRecentCompleted.get(); - - assertThat(commandLogEntry.getInteractionId()).isNotNull(); - assertThat(commandLogEntry.getCompletedAt()).isNotNull(); - assertThat(commandLogEntry.getDuration()).isNotNull(); - assertThat(commandLogEntry.getException()).isEqualTo(""); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingMixin"); - assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); - assertThat(commandLogEntry.getResult()).isNotNull(); - assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK"); - assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); - assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); - assertThat(commandLogEntry.getTarget()).isNotNull(); - assertThat(commandLogEntry.getTimestamp()).isNotNull(); - assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); - assertThat(commandLogEntry.getCommandDto()).isNotNull(); - CommandDto commandDto = commandLogEntry.getCommandDto(); - assertThat(commandDto).isNotNull(); - assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class); - assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); - }); + interactionService.nextInteraction(); + + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isPresent(); + + CommandLogEntry commandLogEntry = mostRecentCompleted.get(); + + assertThat(commandLogEntry.getInteractionId()).isNotNull(); + assertThat(commandLogEntry.getCompletedAt()).isNotNull(); + assertThat(commandLogEntry.getDuration()).isNotNull(); + assertThat(commandLogEntry.getException()).isEqualTo(""); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingMixin"); + assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); + assertThat(commandLogEntry.getResult()).isNotNull(); + assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK"); + assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); + assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); + assertThat(commandLogEntry.getTarget()).isNotNull(); + assertThat(commandLogEntry.getTimestamp()).isNotNull(); + assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); + assertThat(commandLogEntry.getCommandDto()).isNotNull(); + CommandDto commandDto = commandLogEntry.getCommandDto(); + assertThat(commandDto).isNotNull(); + assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class); + assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); } @Test @@ -140,56 +125,57 @@ void invoke_direct() { // when wrapperFactory.wrap(counter1).bumpUsingDeclaredAction(); - testSupport.nextInteraction(ia->{ - - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isPresent(); - - CommandLogEntry commandLogEntry = mostRecentCompleted.get(); - - assertThat(commandLogEntry.getInteractionId()).isNotNull(); - assertThat(commandLogEntry.getCompletedAt()).isNotNull(); - assertThat(commandLogEntry.getDuration()).isNotNull(); - assertThat(commandLogEntry.getException()).isEqualTo(""); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingDeclaredAction"); - assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); - assertThat(commandLogEntry.getResult()).isNotNull(); - assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK"); - assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); - assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); - assertThat(commandLogEntry.getTarget()).isNotNull(); - assertThat(commandLogEntry.getTimestamp()).isNotNull(); - assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); - assertThat(commandLogEntry.getCommandDto()).isNotNull(); - CommandDto commandDto = commandLogEntry.getCommandDto(); - assertThat(commandDto).isNotNull(); - assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class); - assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); - }); + interactionService.nextInteraction(); + + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isPresent(); + + CommandLogEntry commandLogEntry = mostRecentCompleted.get(); + + assertThat(commandLogEntry.getInteractionId()).isNotNull(); + assertThat(commandLogEntry.getCompletedAt()).isNotNull(); + assertThat(commandLogEntry.getDuration()).isNotNull(); + assertThat(commandLogEntry.getException()).isEqualTo(""); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#bumpUsingDeclaredAction"); + assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); + assertThat(commandLogEntry.getResult()).isNotNull(); + assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK"); + assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); + assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); + assertThat(commandLogEntry.getTarget()).isNotNull(); + assertThat(commandLogEntry.getTimestamp()).isNotNull(); + assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); + assertThat(commandLogEntry.getCommandDto()).isNotNull(); + CommandDto commandDto = commandLogEntry.getCommandDto(); + assertThat(commandDto).isNotNull(); + assertThat(commandDto.getMember()).isInstanceOf(ActionDto.class); + assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); } @Test void invoke_mixin_disabled() { + // when wrapperFactory.wrapMixin(Counter_bumpUsingMixinWithCommandPublishingDisabled.class, counter1).act(); - testSupport.nextInteraction(ia->{ - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isEmpty(); - }); + interactionService.nextInteraction(); + + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isEmpty(); } @Test void invoke_direct_disabled() { + // when wrapperFactory.wrap(counter1).bumpUsingDeclaredActionWithCommandPublishingDisabled(); - testSupport.nextInteraction(ia->{ - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isEmpty(); - }); + interactionService.nextInteraction(); + + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isEmpty(); } @Test @@ -197,33 +183,32 @@ void edit() { // when wrapperFactory.wrap(counter1).setNum(99L); - testSupport.nextInteraction(ia->{ - - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isPresent(); - - CommandLogEntry commandLogEntry = mostRecentCompleted.get(); - - assertThat(commandLogEntry.getInteractionId()).isNotNull(); - assertThat(commandLogEntry.getCompletedAt()).isNotNull(); - assertThat(commandLogEntry.getDuration()).isNotNull(); - assertThat(commandLogEntry.getException()).isEqualTo(""); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); - assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#num"); - assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); - assertThat(commandLogEntry.getResult()).isNull(); - assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK (VOID)"); - assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); - assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); - assertThat(commandLogEntry.getTarget()).isNotNull(); - assertThat(commandLogEntry.getTimestamp()).isNotNull(); - assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); - CommandDto commandDto = commandLogEntry.getCommandDto(); - assertThat(commandDto).isNotNull(); - assertThat(commandDto.getMember()).isInstanceOf(PropertyDto.class); - assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); - }); + interactionService.nextInteraction(); + + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isPresent(); + + CommandLogEntry commandLogEntry = mostRecentCompleted.get(); + + assertThat(commandLogEntry.getInteractionId()).isNotNull(); + assertThat(commandLogEntry.getCompletedAt()).isNotNull(); + assertThat(commandLogEntry.getDuration()).isNotNull(); + assertThat(commandLogEntry.getException()).isEqualTo(""); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isNotNull(); + assertThat(commandLogEntry.getLogicalMemberIdentifier()).isEqualTo("commandlog.test.Counter#num"); + assertThat(commandLogEntry.getUsername()).isEqualTo("__system"); + assertThat(commandLogEntry.getResult()).isNull(); + assertThat(commandLogEntry.getResultSummary()).isEqualTo("OK (VOID)"); + assertThat(commandLogEntry.getReplayState()).isEqualTo(ReplayState.UNDEFINED); + assertThat(commandLogEntry.getReplayStateFailureReason()).isNull(); + assertThat(commandLogEntry.getTarget()).isNotNull(); + assertThat(commandLogEntry.getTimestamp()).isNotNull(); + assertThat(commandLogEntry.getType()).isEqualTo(DomainChangeRecord.ChangeType.COMMAND); + CommandDto commandDto = commandLogEntry.getCommandDto(); + assertThat(commandDto).isNotNull(); + assertThat(commandDto.getMember()).isInstanceOf(PropertyDto.class); + assertThat(commandDto.getMember().getLogicalMemberIdentifier()).isEqualTo(commandLogEntry.getLogicalMemberIdentifier()); } @Test @@ -231,271 +216,262 @@ void edit_disabled() { // when wrapperFactory.wrap(counter1).setNum2(99L); - testSupport.nextInteraction(ia->{ + interactionService.closeInteractionLayers(); // to flush - // then - Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - assertThat(mostRecentCompleted).isEmpty(); + interactionService.openInteraction(); - }); + // then + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); + assertThat(mostRecentCompleted).isEmpty(); } @Test void roundtrip_CLE_bookmarks() { - class Model { - CommandDto commandDto; - Bookmark cleBookmark; - } - var testSupport = interactionService.testSupport(new Model()); - // given wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - testSupport.nextInteraction(model->{ + interactionService.nextInteraction(); - CommandLogEntry commandLogEntry = commandLogEntryRepository.findMostRecentCompleted().get(); - model.commandDto = commandLogEntry.getCommandDto(); + Optional mostRecentCompleted = commandLogEntryRepository.findMostRecentCompleted(); - // when - Optional cleBookmarkIfAny = bookmarkService.bookmarkFor(commandLogEntry); - - // then - assertThat(cleBookmarkIfAny).isPresent(); - model.cleBookmark = cleBookmarkIfAny.get(); - String identifier = model.cleBookmark.identifier(); - if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { - assertThat(identifier).startsWith("u_"); - UUID.fromString(identifier.substring("u_".length())); // should not fail, ie check the format is as we expect - } else { - UUID.fromString(identifier); // should not fail, ie check the format is as we expect - } + CommandLogEntry commandLogEntry = mostRecentCompleted.get(); + CommandDto commandDto = commandLogEntry.getCommandDto(); - }); + // when + Optional cleBookmarkIfAny = bookmarkService.bookmarkFor(commandLogEntry); + + // then + assertThat(cleBookmarkIfAny).isPresent(); + Bookmark cleBookmark = cleBookmarkIfAny.get(); + String identifier = cleBookmark.identifier(); + if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { + assertThat(identifier).startsWith("u_"); + UUID.fromString(identifier.substring("u_".length())); // should not fail, ie check the format is as we expect + } else { + UUID.fromString(identifier); // should not fail, ie check the format is as we expect + } // when we start a new session and lookup from the bookmark - testSupport.nextInteraction(model->{ + interactionService.nextInteraction(); - Optional cle2IfAny = bookmarkService.lookup(model.cleBookmark); - assertThat(cle2IfAny).isPresent(); + Optional cle2IfAny = bookmarkService.lookup(cleBookmarkIfAny.get()); + assertThat(cle2IfAny).isPresent(); - CommandLogEntry cle2 = (CommandLogEntry) cle2IfAny.get(); - CommandDto commandDto2 = cle2.getCommandDto(); + CommandLogEntry cle2 = (CommandLogEntry) cle2IfAny.get(); + CommandDto commandDto2 = cle2.getCommandDto(); - assertThat(commandDto2).isEqualTo(model.commandDto); - }); + assertThat(commandDto2).isEqualTo(commandDto); } @Test void test_all_the_repository_methods() { - class Model { - UUID commandTarget1User1Id; - UUID commandTarget1User2Id; - UUID commandTarget1User1YesterdayId; - } - var testSupport = interactionService.testSupport(new Model()); - // given sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); }); + interactionService.nextInteraction(); - testSupport.nextInteraction(model->{ - // when - Optional commandTarget1User1IfAny = commandLogEntryRepository.findMostRecentCompleted(); + // when + Optional commandTarget1User1IfAny = commandLogEntryRepository.findMostRecentCompleted(); + + // then + Assertions.assertThat(commandTarget1User1IfAny).isPresent(); + var commandTarget1User1 = commandTarget1User1IfAny.get(); + var commandTarget1User1Id = commandTarget1User1.getInteractionId(); + + // given (different user, same target, same day) + counter1 = counterRepository.findByName("counter-1"); + sudoService.run( + InteractionContext.switchUser( + UserMemento.builder("user-2").build()), + () -> wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act() + ); + interactionService.nextInteraction(); - // then - Assertions.assertThat(commandTarget1User1IfAny).isPresent(); - var commandTarget1User1 = commandTarget1User1IfAny.get(); - model.commandTarget1User1Id = commandTarget1User1.getInteractionId(); - - // given (different user, same target, same day) - counter1 = counterRepository.findByName("counter-1"); - sudoService.run( - InteractionContext.switchUser( - UserMemento.builder("user-2").build()), - () -> wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act() - ); + // when + Optional commandTarget1User2IfAny = commandLogEntryRepository.findMostRecentCompleted(); + + // then + Assertions.assertThat(commandTarget1User2IfAny).isPresent(); + var commandTarget1User2 = commandTarget1User2IfAny.get(); + var commandTarget1User2Id = commandTarget1User2.getInteractionId(); + + // given (same user, same target, yesterday) + counter1 = counterRepository.findByName("counter-1"); + final UUID[] commandTarget1User1YesterdayIdHolder = new UUID[1]; + sudoService.run( + InteractionContext.switchUser( + UserMemento.builder("user-1").build()), + () -> { + var yesterday = clockService.getClock().nowAsLocalDateTime().minusDays(1); + sudoService.run( + InteractionContext.switchClock(VirtualClock.nowAt(yesterday)), + () -> { + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); + commandTarget1User1YesterdayIdHolder[0] = interactionLayerTracker.currentInteraction().get().getInteractionId(); + interactionService.closeInteractionLayers(); // to flush within changed time... + } + ); + }); + interactionService.openInteraction(); + + // when, then + final UUID commandTarget1User1YesterdayId = commandTarget1User1YesterdayIdHolder[0]; + + // given (same user, different target, same day) + counter2 = counterRepository.findByName("counter-2"); + sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); }); + interactionService.nextInteraction(); - testSupport.nextInteraction(model->{ - // when - Optional commandTarget1User2IfAny = commandLogEntryRepository.findMostRecentCompleted(); + // when + Optional commandTarget2User1IfAny = commandLogEntryRepository.findMostRecentCompleted(); - // then - Assertions.assertThat(commandTarget1User2IfAny).isPresent(); - var commandTarget1User2 = commandTarget1User2IfAny.get(); - model.commandTarget1User2Id = commandTarget1User2.getInteractionId(); - - // given (same user, same target, yesterday) - counter1 = counterRepository.findByName("counter-1"); - sudoService.run( - InteractionContext.switchUser( - UserMemento.builder("user-1").build()), - () -> { - var yesterday = clockService.getClock().nowAsLocalDateTime().minusDays(1); - sudoService.run( - InteractionContext.switchClock(VirtualClock.nowAt(yesterday)), - () -> { - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - // when, then - model.commandTarget1User1YesterdayId = interactionLayerTracker.currentInteraction().get().getInteractionId(); - interactionService.closeInteractionLayers(); // to flush within changed time... - } - ); - }); - interactionService.openInteraction(); - - // given (same user, different target, same day) - counter2 = counterRepository.findByName("counter-2"); - sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); - }); - }); + // then + Assertions.assertThat(commandTarget2User1IfAny).isPresent(); + var commandTarget2User1 = commandTarget2User1IfAny.get(); + var commandTarget2User1Id = commandTarget2User1.getInteractionId(); + + // when + Optional commandTarget1User1ById = commandLogEntryRepository.findByInteractionId(commandTarget1User1Id); + Optional commandTarget1User2ById = commandLogEntryRepository.findByInteractionId(commandTarget1User2Id); + Optional commandTarget1User1YesterdayById = commandLogEntryRepository.findByInteractionId(commandTarget1User1YesterdayId); + Optional commandTarget2User1ById = commandLogEntryRepository.findByInteractionId(commandTarget2User1Id); + + // then + Assertions.assertThat(commandTarget1User1ById).isPresent(); + Assertions.assertThat(commandTarget1User2ById).isPresent(); + Assertions.assertThat(commandTarget1User1YesterdayById).isPresent(); + Assertions.assertThat(commandTarget2User1ById).isPresent(); + Assertions.assertThat(commandTarget2User1ById.get()).isSameAs(commandTarget2User1); - testSupport.nextInteraction(model->{ + // given + commandTarget1User1 = commandTarget1User1ById.get(); + commandTarget1User2 = commandTarget1User2ById.get(); + @SuppressWarnings("unused") + var commandTarget1User1Yesterday = commandTarget1User1YesterdayById.get(); + commandTarget2User1 = commandTarget2User1ById.get(); - var commandTarget1User1Id = model.commandTarget1User1Id; - var commandTarget1User2Id = model.commandTarget1User2Id; - var commandTarget1User1YesterdayId = model.commandTarget1User1YesterdayId; + var target1 = commandTarget1User1.getTarget(); + var username1 = commandTarget1User1.getUsername(); + var from = commandTarget1User1.getStartedAt().toLocalDateTime().toLocalDate(); + var to = from.plusDays(1); - // when - Optional commandTarget2User1IfAny = commandLogEntryRepository.findMostRecentCompleted(); + // when + List notYetReplayed = commandLogEntryRepository.findNotYetReplayed(); - // then - Assertions.assertThat(commandTarget2User1IfAny).isPresent(); - var commandTarget2User1 = commandTarget2User1IfAny.get(); - var commandTarget2User1Id = commandTarget2User1.getInteractionId(); + // then + Assertions.assertThat(notYetReplayed).isEmpty(); - // when - Optional commandTarget1User1ById = commandLogEntryRepository.findByInteractionId(commandTarget1User1Id); - Optional commandTarget1User2ById = commandLogEntryRepository.findByInteractionId(commandTarget1User2Id); - Optional commandTarget1User1YesterdayById = commandLogEntryRepository.findByInteractionId(commandTarget1User1YesterdayId); - Optional commandTarget2User1ById = commandLogEntryRepository.findByInteractionId(commandTarget2User1Id); + if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { - // then - Assertions.assertThat(commandTarget1User1ById).isPresent(); - Assertions.assertThat(commandTarget1User2ById).isPresent(); - Assertions.assertThat(commandTarget1User1YesterdayById).isPresent(); - Assertions.assertThat(commandTarget2User1ById).isPresent(); - Assertions.assertThat(commandTarget2User1ById.get()).isSameAs(commandTarget2User1); + // fails in JPA; possibly need to get the agent working for dirty tracking. // given - var commandTarget1User1 = commandTarget1User1ById.get(); - var commandTarget1User2 = commandTarget1User2ById.get(); - @SuppressWarnings("unused") - var commandTarget1User1Yesterday = commandTarget1User1YesterdayById.get(); - commandTarget2User1 = commandTarget2User1ById.get(); - - var target1 = commandTarget1User1.getTarget(); - var username1 = commandTarget1User1.getUsername(); - var from = commandTarget1User1.getStartedAt().toLocalDateTime().toLocalDate(); - var to = from.plusDays(1); + commandTarget1User1.setReplayState(ReplayState.PENDING); // when - List notYetReplayed = commandLogEntryRepository.findNotYetReplayed(); + List notYetReplayed2 = commandLogEntryRepository.findNotYetReplayed(); // then - Assertions.assertThat(notYetReplayed).isEmpty(); + Assertions.assertThat(notYetReplayed2).hasSize(1); + Assertions.assertThat(notYetReplayed2.get(0).getInteractionId()).isEqualTo(commandTarget1User1.getInteractionId()); + } - if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { + // when + List byFromAndTo = commandLogEntryRepository.findByFromAndTo(from, to); - // fails in JPA; possibly need to get the agent working for dirty tracking. + // then + Assertions.assertThat(byFromAndTo).hasSize(3); + Assertions.assertThat(byFromAndTo.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent - // given - commandTarget1User1.setReplayState(ReplayState.PENDING); + // when + List byTarget1AndFromAndTo = commandLogEntryRepository.findByTargetAndFromAndTo(target1, from, to); - // when - List notYetReplayed2 = commandLogEntryRepository.findNotYetReplayed(); + // then + Assertions.assertThat(byTarget1AndFromAndTo).hasSize(2); + Assertions.assertThat(byTarget1AndFromAndTo.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // the more recent - // then - Assertions.assertThat(notYetReplayed2).hasSize(1); - Assertions.assertThat(notYetReplayed2.get(0).getInteractionId()).isEqualTo(commandTarget1User1.getInteractionId()); - } + // when + List recentByTargetOfCommand1 = commandLogEntryRepository.findRecentByTarget(target1); - // when - List byFromAndTo = commandLogEntryRepository.findByFromAndTo(from, to); + // then + Assertions.assertThat(recentByTargetOfCommand1).hasSize(3); + Assertions.assertThat(recentByTargetOfCommand1.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // the more recent - // then - Assertions.assertThat(byFromAndTo).hasSize(3); - Assertions.assertThat(byFromAndTo.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent + // when + List recentByUsername = commandLogEntryRepository.findRecentByUsername(username1); - // when - List byTarget1AndFromAndTo = commandLogEntryRepository.findByTargetAndFromAndTo(target1, from, to); + // then + Assertions.assertThat(recentByUsername).hasSize(3); + Assertions.assertThat(recentByUsername.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent - // then - Assertions.assertThat(byTarget1AndFromAndTo).hasSize(2); - Assertions.assertThat(byTarget1AndFromAndTo.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // the more recent + // when + List byParent = commandLogEntryRepository.findByParent(commandTarget1User1); - // when - List recentByTargetOfCommand1 = commandLogEntryRepository.findRecentByTarget(target1); + // then // TODO: would need nested executions for this to show up. + Assertions.assertThat(byParent).isEmpty(); - // then - Assertions.assertThat(recentByTargetOfCommand1).hasSize(3); - Assertions.assertThat(recentByTargetOfCommand1.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // the more recent + // when + List completed = commandLogEntryRepository.findCompleted(); - // when - List recentByUsername = commandLogEntryRepository.findRecentByUsername(username1); + // then + Assertions.assertThat(completed).hasSize(4); + Assertions.assertThat(completed.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent - // then - Assertions.assertThat(recentByUsername).hasSize(3); - Assertions.assertThat(recentByUsername.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent + // when + List current = commandLogEntryRepository.findCurrent(); - // when - List byParent = commandLogEntryRepository.findByParent(commandTarget1User1); + // then // TODO: would need more sophistication in fixtures to test + Assertions.assertThat(current).isEmpty(); - // then // TODO: would need nested executions for this to show up. - Assertions.assertThat(byParent).isEmpty(); + // when + List since = commandLogEntryRepository.findSince(commandTarget1User1.getInteractionId(), 3); - // when - List completed = commandLogEntryRepository.findCompleted(); + // then + Assertions.assertThat(since).hasSize(2); + Assertions.assertThat(since.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // oldest first - // then - Assertions.assertThat(completed).hasSize(4); - Assertions.assertThat(completed.get(0).getInteractionId()).isEqualTo(commandTarget2User1.getInteractionId()); // the more recent + // when + List sinceWithBatchSize1 = commandLogEntryRepository.findSince(commandTarget1User1.getInteractionId(), 1); - // when - List current = commandLogEntryRepository.findCurrent(); + // then + Assertions.assertThat(sinceWithBatchSize1).hasSize(1); + Assertions.assertThat(sinceWithBatchSize1.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // oldest fist - // then // TODO: would need more sophistication in fixtures to test - Assertions.assertThat(current).isEmpty(); + // when + Optional mostRecentReplayedIfAny = commandLogEntryRepository.findMostRecentReplayed(); - // when - List since = commandLogEntryRepository.findSince(commandTarget1User1.getInteractionId(), 3); + // then + Assertions.assertThat(mostRecentReplayedIfAny).isEmpty(); - // then - Assertions.assertThat(since).hasSize(2); - Assertions.assertThat(since.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // oldest first + if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { - // when - List sinceWithBatchSize1 = commandLogEntryRepository.findSince(commandTarget1User1.getInteractionId(), 1); + // fails in JPA; possibly need to get the agent working for dirty tracking. - // then - Assertions.assertThat(sinceWithBatchSize1).hasSize(1); - Assertions.assertThat(sinceWithBatchSize1.get(0).getInteractionId()).isEqualTo(commandTarget1User2.getInteractionId()); // oldest fist + // given + commandTarget1User1.setReplayState(ReplayState.OK); // when - Optional mostRecentReplayedIfAny = commandLogEntryRepository.findMostRecentReplayed(); + Optional mostRecentReplayedIfAny2 = commandLogEntryRepository.findMostRecentReplayed(); // then - Assertions.assertThat(mostRecentReplayedIfAny).isEmpty(); - - if (causewayBeanTypeRegistry.persistenceStack().isJdo()) { - - // fails in JPA; possibly need to get the agent working for dirty tracking. - - // given - commandTarget1User1.setReplayState(ReplayState.OK); - - // when - Optional mostRecentReplayedIfAny2 = commandLogEntryRepository.findMostRecentReplayed(); - - // then - Assertions.assertThat(mostRecentReplayedIfAny2).isPresent(); - Assertions.assertThat(mostRecentReplayedIfAny2.get().getInteractionId()).isEqualTo(commandTarget1User1Id); - } - }); + Assertions.assertThat(mostRecentReplayedIfAny2).isPresent(); + Assertions.assertThat(mostRecentReplayedIfAny2.get().getInteractionId()).isEqualTo(commandTarget1User1Id); + } } + @Inject CommandLogEntryRepository commandLogEntryRepository; + @Inject SudoService sudoService; + @Inject ClockService clockService; + @Inject InteractionService interactionService; + @Inject InteractionLayerTracker interactionLayerTracker; + @Inject CounterRepository counterRepository; + @Inject WrapperFactory wrapperFactory; + @Inject BookmarkService bookmarkService; + @Inject CausewayBeanTypeRegistry causewayBeanTypeRegistry; + } diff --git a/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java b/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java index 77e4b7c623a..f235a176dfc 100644 --- a/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java +++ b/extensions/core/executionlog/applib/src/test/java/org/apache/causeway/extensions/executionlog/applib/integtest/ExecutionLog_IntegTestAbstract.java @@ -39,7 +39,6 @@ import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker; import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.sudo.SudoService; import org.apache.causeway.applib.services.user.UserMemento; import org.apache.causeway.applib.services.wrapper.WrapperFactory; @@ -57,15 +56,6 @@ public abstract class ExecutionLog_IntegTestAbstract extends CausewayIntegrationTestAbstract { - @Inject ExecutionLogEntryRepository executionLogEntryRepository; - @Inject SudoService sudoService; - @Inject ClockService clockService; - @Inject InteractionService interactionService; - @Inject InteractionLayerTracker interactionLayerTracker; - @Inject CounterRepository counterRepository; - @Inject WrapperFactory wrapperFactory; - @Inject BookmarkService bookmarkService; - @BeforeAll static void beforeAll() { CausewayPresets.forcePrototyping(); @@ -73,11 +63,10 @@ static void beforeAll() { Counter counter1; Counter counter2; - private TestSupport testSupport; @BeforeEach void beforeEach() { - this.testSupport = interactionService.testSupport(); + counterRepository.removeAll(); executionLogEntryRepository.removeAll(); @@ -234,29 +223,21 @@ void roundtrip_ELE_bookmarks() { Integer.parseInt(identifier.substring(identifier.indexOf("_")+1)); // should not fail, ie check the format is as we expect // when we start a new session and lookup from the bookmark - testSupport.nextInteraction(model->{ + interactionService.nextInteraction(); - Optional cle2IfAny = bookmarkService.lookup(eleBookmarkIfAny.get()); - assertThat(cle2IfAny).isPresent(); + Optional cle2IfAny = bookmarkService.lookup(eleBookmarkIfAny.get()); + assertThat(cle2IfAny).isPresent(); - ExecutionLogEntry ele2 = (ExecutionLogEntry) cle2IfAny.get(); - InteractionDto interactionDto2 = ele2.getInteractionDto(); + ExecutionLogEntry ele2 = (ExecutionLogEntry) cle2IfAny.get(); + InteractionDto interactionDto2 = ele2.getInteractionDto(); - assertThat(interactionDto2).isEqualTo(interactionDto); - }); + assertThat(interactionDto2).isEqualTo(interactionDto); } @Test void test_all_the_repository_methods() { - class Model { - UUID executionTarget1User1Id; - UUID executionTarget1User2Id; - UUID executionTarget1User1YesterdayId; - } - var testSupport = interactionService.testSupport(new Model()); - // given sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); @@ -267,10 +248,8 @@ class Model { // then assertThat(executionsForTarget1User1).hasSize(1); - { - var executionTarget1User1 = executionsForTarget1User1.get(0); - testSupport.model().executionTarget1User1Id = executionTarget1User1.getInteractionId(); - } + var executionTarget1User1 = executionsForTarget1User1.get(0); + var executionTarget1User1Id = executionTarget1User1.getInteractionId(); // given (different user, same target, same day) counter1 = counterRepository.findByName("counter-1"); @@ -279,134 +258,136 @@ class Model { UserMemento.builder("user-2").build()), () -> wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act() ); + interactionService.nextInteraction(); - testSupport.nextInteraction(model->{ - - // when - List executionsForTarget1User2 = executionLogEntryRepository.findMostRecent(1); - - // then - assertThat(executionsForTarget1User2).hasSize(1); - var executionTarget1User2 = executionsForTarget1User2.get(0); - model.executionTarget1User2Id = executionTarget1User2.getInteractionId(); - - // given (same user, same target, yesterday) - counter1 = counterRepository.findByName("counter-1"); - - sudoService.run( - InteractionContext.switchUser( - UserMemento.builder("user-1").build()), - () -> { - var yesterday = clockService.getClock().nowAsLocalDateTime().minusDays(1); - sudoService.run( - InteractionContext.switchClock(VirtualClock.nowAt(yesterday)), - () -> { - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); - // when, then - model.executionTarget1User1YesterdayId = interactionLayerTracker.currentInteraction().get().getInteractionId(); - interactionService.closeInteractionLayers(); // to flush within changed time... - } - ); - }); - }); + // when + List executionsForTarget1User2 = executionLogEntryRepository.findMostRecent(1); + // then + assertThat(executionsForTarget1User2).hasSize(1); + var executionTarget1User2 = executionsForTarget1User2.get(0); + var executionTarget1User2Id = executionTarget1User2.getInteractionId(); + + // given (same user, same target, yesterday) + counter1 = counterRepository.findByName("counter-1"); + final UUID[] executionTarget1User1YesterdayIdHolder = new UUID[1]; + sudoService.run( + InteractionContext.switchUser( + UserMemento.builder("user-1").build()), + () -> { + var yesterday = clockService.getClock().nowAsLocalDateTime().minusDays(1); + sudoService.run( + InteractionContext.switchClock(VirtualClock.nowAt(yesterday)), + () -> { + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); + executionTarget1User1YesterdayIdHolder[0] = interactionLayerTracker.currentInteraction().get().getInteractionId(); + interactionService.closeInteractionLayers(); // to flush within changed time... + } + ); + }); interactionService.openInteraction(); + // when, then + final UUID executionTarget1User1YesterdayId = executionTarget1User1YesterdayIdHolder[0]; + // given (same user, different target, same day) counter2 = counterRepository.findByName("counter-2"); sudoService.run(InteractionContext.switchUser(UserMemento.builder("user-1").build()), () -> { wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); }); + interactionService.nextInteraction(); - testSupport.nextInteraction(model->{ - - var executionTarget1User1Id = model.executionTarget1User1Id; - var executionTarget1User2Id = model.executionTarget1User2Id; - var executionTarget1User1YesterdayId = model.executionTarget1User1YesterdayId; - - // when - List executionTarget2User1IfAny = executionLogEntryRepository.findMostRecent(1); + // when + List executionTarget2User1IfAny = executionLogEntryRepository.findMostRecent(1); - // then - assertThat(executionTarget2User1IfAny).hasSize(1); - var executionTarget2User1 = executionTarget2User1IfAny.get(0); - var executionTarget2User1Id = executionTarget2User1.getInteractionId(); + // then + assertThat(executionTarget2User1IfAny).hasSize(1); + var executionTarget2User1 = executionTarget2User1IfAny.get(0); + var executionTarget2User1Id = executionTarget2User1.getInteractionId(); - // when - Optional executionTarget1User1ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User1Id, 0); - Optional executionTarget1User2ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User2Id, 0); - Optional executionTarget1User1YesterdayById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User1YesterdayId, 0); - Optional executionTarget2User1ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget2User1Id, 0); + // when + Optional executionTarget1User1ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User1Id, 0); + Optional executionTarget1User2ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User2Id, 0); + Optional executionTarget1User1YesterdayById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget1User1YesterdayId, 0); + Optional executionTarget2User1ById = executionLogEntryRepository.findByInteractionIdAndSequence(executionTarget2User1Id, 0); - // then - assertThat(executionTarget1User1ById).isPresent(); - assertThat(executionTarget1User2ById).isPresent(); - assertThat(executionTarget1User1YesterdayById).isPresent(); - assertThat(executionTarget2User1ById).isPresent(); - assertThat(executionTarget2User1ById.get()).isSameAs(executionTarget2User1); + // then + assertThat(executionTarget1User1ById).isPresent(); + assertThat(executionTarget1User2ById).isPresent(); + assertThat(executionTarget1User1YesterdayById).isPresent(); + assertThat(executionTarget2User1ById).isPresent(); + assertThat(executionTarget2User1ById.get()).isSameAs(executionTarget2User1); - // given - counter1 = counterRepository.findByName("counter-1"); - var executionTarget1User1 = executionTarget1User1ById.get(); - var executionTarget1User2 = executionTarget1User2ById.get(); - var executionTarget1User1Yesterday = executionTarget1User1YesterdayById.get(); - executionTarget2User1 = executionTarget2User1ById.get(); + // given + counter1 = counterRepository.findByName("counter-1"); + executionTarget1User1 = executionTarget1User1ById.get(); + executionTarget1User2 = executionTarget1User2ById.get(); + var executionTarget1User1Yesterday = executionTarget1User1YesterdayById.get(); + executionTarget2User1 = executionTarget2User1ById.get(); - var target1 = executionTarget1User1.getTarget(); - var username1 = executionTarget1User1.getUsername(); - Timestamp from1 = executionTarget1User1.getStartedAt(); - Timestamp to1 = Timestamp.valueOf(from1.toLocalDateTime().plusDays(1)); - var bookmark1 = bookmarkService.bookmarkForElseFail(counter1); + var target1 = executionTarget1User1.getTarget(); + var username1 = executionTarget1User1.getUsername(); + Timestamp from1 = executionTarget1User1.getStartedAt(); + Timestamp to1 = Timestamp.valueOf(from1.toLocalDateTime().plusDays(1)); + var bookmark1 = bookmarkService.bookmarkForElseFail(counter1); - // when - List recentByTarget = executionLogEntryRepository.findRecentByTarget(bookmark1); + // when + List recentByTarget = executionLogEntryRepository.findRecentByTarget(bookmark1); - // then - assertThat(recentByTarget).hasSize(3); + // then + assertThat(recentByTarget).hasSize(3); - // when - List byTargetAndTimestampBefore = executionLogEntryRepository.findByTargetAndTimestampBefore(bookmark1, from1); + // when + List byTargetAndTimestampBefore = executionLogEntryRepository.findByTargetAndTimestampBefore(bookmark1, from1); - // then - assertThat(byTargetAndTimestampBefore).hasSize(2); // yesterday, plus cmd1 + // then + assertThat(byTargetAndTimestampBefore).hasSize(2); // yesterday, plus cmd1 - // when - List byTargetAndTimestampAfter = executionLogEntryRepository.findByTargetAndTimestampAfter(bookmark1, from1); + // when + List byTargetAndTimestampAfter = executionLogEntryRepository.findByTargetAndTimestampAfter(bookmark1, from1); - // then - assertThat(byTargetAndTimestampAfter).hasSize(2); // cmd1, 2nd + // then + assertThat(byTargetAndTimestampAfter).hasSize(2); // cmd1, 2nd - // when - List byTargetAndTimestampBetween = executionLogEntryRepository.findByTargetAndTimestampBetween(bookmark1, from1, to1); + // when + List byTargetAndTimestampBetween = executionLogEntryRepository.findByTargetAndTimestampBetween(bookmark1, from1, to1); - // then - assertThat(byTargetAndTimestampBetween).hasSize(2); // 1st and 2nd for this target + // then + assertThat(byTargetAndTimestampBetween).hasSize(2); // 1st and 2nd for this target - // when - List byTimestampBefore = executionLogEntryRepository.findByTimestampBefore(from1); + // when + List byTimestampBefore = executionLogEntryRepository.findByTimestampBefore(from1); - // then - assertThat(byTimestampBefore).hasSize(2); // cmd1 plus yesterday + // then + assertThat(byTimestampBefore).hasSize(2); // cmd1 plus yesterday - // when - List byTimestampAfter = executionLogEntryRepository.findByTimestampAfter(from1); + // when + List byTimestampAfter = executionLogEntryRepository.findByTimestampAfter(from1); - // then - assertThat(byTimestampAfter).hasSize(3); // cmd1, 2nd, and for other target + // then + assertThat(byTimestampAfter).hasSize(3); // cmd1, 2nd, and for other target - // when - List byTimestampBetween = executionLogEntryRepository.findByTimestampBetween(from1, to1); + // when + List byTimestampBetween = executionLogEntryRepository.findByTimestampBetween(from1, to1); - // then - assertThat(byTimestampBetween).hasSize(3); // 1st and 2nd for this target, and other target + // then + assertThat(byTimestampBetween).hasSize(3); // 1st and 2nd for this target, and other target - // when - List byUsername = executionLogEntryRepository.findRecentByUsername(username1); + // when + List byUsername = executionLogEntryRepository.findRecentByUsername(username1); - // then - assertThat(byUsername).hasSize(3); - }); + // then + assertThat(byUsername).hasSize(3); } + @Inject ExecutionLogEntryRepository executionLogEntryRepository; + @Inject SudoService sudoService; + @Inject ClockService clockService; + @Inject InteractionService interactionService; + @Inject InteractionLayerTracker interactionLayerTracker; + @Inject CounterRepository counterRepository; + @Inject WrapperFactory wrapperFactory; + @Inject BookmarkService bookmarkService; + } diff --git a/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java b/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java index 9455cc3d7a9..c35fe6211d9 100644 --- a/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java +++ b/extensions/core/executionoutbox/applib/src/test/java/org/apache/causeway/extensions/executionoutbox/applib/integtest/ExecutionOutbox_IntegTestAbstract.java @@ -36,7 +36,6 @@ import org.apache.causeway.applib.services.clock.ClockService; import org.apache.causeway.applib.services.iactnlayer.InteractionContext; import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.sudo.SudoService; import org.apache.causeway.applib.services.user.UserMemento; import org.apache.causeway.applib.services.wrapper.WrapperFactory; @@ -61,11 +60,9 @@ static void beforeAll() { Counter counter1; Counter counter2; - private TestSupport testSupport; @BeforeEach void beforeEach() { - this.testSupport = interactionService.testSupport(); counterRepository.removeAll(); executionOutboxEntryRepository.removeAll(); diff --git a/extensions/security/audittrail/applib/src/test/java/org/apache/causeway/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java b/extensions/security/audittrail/applib/src/test/java/org/apache/causeway/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java index 54042cafebc..d182ff274b2 100644 --- a/extensions/security/audittrail/applib/src/test/java/org/apache/causeway/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java +++ b/extensions/security/audittrail/applib/src/test/java/org/apache/causeway/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java @@ -31,7 +31,6 @@ import org.apache.causeway.applib.mixins.system.DomainChangeRecord; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.wrapper.WrapperFactory; import org.apache.causeway.core.config.presets.CausewayPresets; import org.apache.causeway.extensions.audittrail.applib.dom.AuditTrailEntry; @@ -43,8 +42,6 @@ public abstract class AuditTrail_IntegTestAbstract extends CausewayIntegrationTestAbstract { - private TestSupport testSupport; - @BeforeAll static void beforeAll() { CausewayPresets.forcePrototyping(); @@ -52,15 +49,14 @@ static void beforeAll() { @BeforeEach void setUp() { - this.testSupport = interactionService.testSupport(); counterRepository.removeAll(); - testSupport.nextInteraction(model->{ - auditTrailEntryRepository.removeAll(); - }); - testSupport.nextInteraction(model->{ - assertThat(counterRepository.find()).isEmpty(); - assertThat(auditTrailEntryRepository.findAll()).isEmpty(); - }); + interactionService.nextInteraction(); + + auditTrailEntryRepository.removeAll(); + interactionService.nextInteraction(); + + assertThat(counterRepository.find()).isEmpty(); + assertThat(auditTrailEntryRepository.findAll()).isEmpty(); } protected abstract Counter newCounter(String name); @@ -71,33 +67,32 @@ void created() { // when var counter1 = counterRepository.persist(newCounter("counter-1")); var target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); - testSupport.nextInteraction(model->{ - - // then - var entries = auditTrailEntryRepository.findAll(); - var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).contains("name", "num", "num2"); - - var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("name")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#name")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("counter-1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isNull()); - assertThat(entriesById.get("num2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isNull()); - }); + interactionService.nextInteraction(); + + // then + var entries = auditTrailEntryRepository.findAll(); + var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).contains("name", "num", "num2"); + + var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("name")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#name")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("counter-1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isNull()); + assertThat(entriesById.get("num2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("[NEW]")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isNull()); } @Test @@ -106,62 +101,55 @@ void updated_using_mixin() { // given var counter1 = counterRepository.persist(newCounter("counter-1")); var target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); - testSupport.nextInteraction(ia->{ - auditTrailEntryRepository.removeAll(); - }); - - testSupport.nextInteraction(ia->{ - assertThat(counterRepository.find()).hasSize(1); - assertThat(auditTrailEntryRepository.findAll()).isEmpty(); - - // when - var counter2 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); - }); - - testSupport.nextInteraction(ia->{ - - // then - var entries = auditTrailEntryRepository.findAll(); - var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).containsExactly("num"); - - var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); - - // given - auditTrailEntryRepository.removeAll(); - - }); - - testSupport.nextInteraction(ia->{ - - // when bump again - var counter2 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter2).act(); - }); - - testSupport.nextInteraction(ia->{ - - // then - var entries = auditTrailEntryRepository.findAll(); - var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).containsExactly("num"); - - var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("2")); - }); + interactionService.nextInteraction(); + + auditTrailEntryRepository.removeAll(); + interactionService.nextInteraction(); + + assertThat(counterRepository.find()).hasSize(1); + assertThat(auditTrailEntryRepository.findAll()).isEmpty(); + + // when + counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); + interactionService.nextInteraction(); + + // then + var entries = auditTrailEntryRepository.findAll(); + var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).containsExactly("num"); + + var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); + + // given + auditTrailEntryRepository.removeAll(); + interactionService.nextInteraction(); + + // when bump again + counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); + wrapperFactory.wrapMixin(Counter_bumpUsingMixin.class, counter1).act(); + interactionService.nextInteraction(); + + // then + entries = auditTrailEntryRepository.findAll(); + propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).containsExactly("num"); + + entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("2")); + } @Test @@ -172,44 +160,40 @@ void deleted() { counter1.setNum(1L); counter1.setNum2(2L); var target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); + interactionService.nextInteraction(); + + auditTrailEntryRepository.removeAll(); + interactionService.nextInteraction(); - testSupport.nextInteraction(ia->{ - auditTrailEntryRepository.removeAll(); - }); - - testSupport.nextInteraction(ia->{ - // when - var counter2 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - counterRepository.remove(counter2); - }); - - testSupport.nextInteraction(ia->{ - - // then - var entries = auditTrailEntryRepository.findAll(); - var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).contains("name", "num", "num2"); - - var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("name")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#name")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("counter-1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")); - assertThat(entriesById.get("num2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("2")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")); - }); + // when + counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); + counterRepository.remove(counter1); + interactionService.nextInteraction(); + + // then + var entries = auditTrailEntryRepository.findAll(); + var propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).contains("name", "num", "num2"); + + var entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("name")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#name")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("counter-1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")); + assertThat(entriesById.get("num2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("audittrail.test.Counter#num2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isEqualTo("2")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("[DELETED]")); } diff --git a/regressiontests/cmdexecauditsess/generic/src/main/java/org/apache/causeway/regressiontests/cmdexecauditsess/generic/integtest/CmdExecAuditSessLog_IntegTestAbstract.java b/regressiontests/cmdexecauditsess/generic/src/main/java/org/apache/causeway/regressiontests/cmdexecauditsess/generic/integtest/CmdExecAuditSessLog_IntegTestAbstract.java index 14ca623834e..3b24b9bc08f 100644 --- a/regressiontests/cmdexecauditsess/generic/src/main/java/org/apache/causeway/regressiontests/cmdexecauditsess/generic/integtest/CmdExecAuditSessLog_IntegTestAbstract.java +++ b/regressiontests/cmdexecauditsess/generic/src/main/java/org/apache/causeway/regressiontests/cmdexecauditsess/generic/integtest/CmdExecAuditSessLog_IntegTestAbstract.java @@ -33,7 +33,6 @@ import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.wrapper.WrapperFactory; import org.apache.causeway.core.config.beans.CausewayBeanTypeRegistry; import org.apache.causeway.core.config.presets.CausewayPresets; @@ -60,35 +59,32 @@ static void beforeAll() { } Bookmark target1; - private TestSupport testSupport; @BeforeEach void beforeEach() { - this.testSupport = interactionService.testSupport(); - testSupport.nextInteraction(ia->{ - counterRepository.removeAll(); - - assertThat(counterRepository.find()).isEmpty(); - - var counter1 = counterRepository.persist(newCounter("counter-1")); - target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); - - assertThat(counterRepository.find()).hasSize(1); - }); - - testSupport.nextInteraction(ia->{ - commandLogEntryRepository.removeAll(); - executionLogEntryRepository.removeAll(); - executionOutboxEntryRepository.removeAll(); - auditTrailEntryRepository.removeAll(); - }); - - testSupport.nextInteraction(ia->{ - assertThat(commandLogEntryRepository.findAll()).isEmpty(); - assertThat(executionLogEntryRepository.findAll()).isEmpty(); - assertThat(executionOutboxEntryRepository.findAll()).isEmpty(); - assertThat(auditTrailEntryRepository.findAll()).isEmpty(); - }); + interactionService.nextInteraction(); + + counterRepository.removeAll(); + + assertThat(counterRepository.find()).isEmpty(); + + var counter1 = counterRepository.persist(newCounter("counter-1")); + target1 = bookmarkService.bookmarkFor(counter1).orElseThrow(); + + assertThat(counterRepository.find()).hasSize(1); + + interactionService.nextInteraction(); + commandLogEntryRepository.removeAll(); + executionLogEntryRepository.removeAll(); + executionOutboxEntryRepository.removeAll(); + auditTrailEntryRepository.removeAll(); + + interactionService.nextInteraction(); + + assertThat(commandLogEntryRepository.findAll()).isEmpty(); + assertThat(executionLogEntryRepository.findAll()).isEmpty(); + assertThat(executionOutboxEntryRepository.findAll()).isEmpty(); + assertThat(auditTrailEntryRepository.findAll()).isEmpty(); } protected abstract Counter newCounter(String name); @@ -97,7 +93,7 @@ void beforeEach() { protected void assertEntityPublishingDisabledFor(final Class entityClass) { var objectSpecification = specificationLoader.loadSpecification(entityClass); - EntityChangePublishingFacet facet = objectSpecification.lookupFacet(EntityChangePublishingFacet.class).orElse(null); + EntityChangePublishingFacet facet = objectSpecification.getFacet(EntityChangePublishingFacet.class); Assertions.assertThat(facet) .satisfies(f -> assertThat(f).isNotNull()) .satisfies(f -> assertThat(f.isEnabled()).isFalse()) @@ -111,8 +107,6 @@ void invoke_mixin() { var counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); var interaction = interactionService.currentInteraction().orElseThrow(); - { - // when wrapperFactory.wrapMixinT(Counter_bumpUsingMixin.class, counter1).act(); @@ -196,46 +190,42 @@ void invoke_mixin() { // ... and audit entries not yet generated var auditTrailEntries = auditTrailEntryRepository.findAll(); assertThat(auditTrailEntries).isEmpty(); - } // when - testSupport.nextInteraction(ia->{ // flushes the command and audit trail entries - - // then - // ... command entry now marked as complete - var commandLogEntries = commandLogEntryRepository.findAll(); - assertThat(commandLogEntries).hasSize(1); - var commandLogEntryAfter = commandLogEntries.get(0); - assertThat(commandLogEntryAfter) - .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) - .satisfies(e -> assertThat(e.getDuration()).isNotNull()) - .satisfies(e -> assertThat(e.getResult()).isNotNull()) - .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK")); - - if(!isJpa()) { - // and then - // ... audit trail entry created - var auditTrailEntries = auditTrailEntryRepository.findAll(); - assertThat(auditTrailEntries).hasSize(1); - - var propertyIds = auditTrailEntries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); - assertThat(propertyIds).containsExactly("num"); - - var entriesById = auditTrailEntries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); - assertThat(entriesById.get("num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("cmdexecauditsess.test.Counter#num")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("1")) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isEqualTo(interaction.getInteractionId())) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) - .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); - } - - }); + interactionService.nextInteraction(); // flushes the command and audit trail entries + // then + // ... command entry now marked as complete + commandLogEntries = commandLogEntryRepository.findAll(); + assertThat(commandLogEntries).hasSize(1); + var commandLogEntryAfter = commandLogEntries.get(0); + assertThat(commandLogEntryAfter) + .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) + .satisfies(e -> assertThat(e.getDuration()).isNotNull()) + .satisfies(e -> assertThat(e.getResult()).isNotNull()) + .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK")); + + if(!isJpa()) { + // and then + // ... audit trail entry created + auditTrailEntries = auditTrailEntryRepository.findAll(); + assertThat(auditTrailEntries).hasSize(1); + + var propertyIds = auditTrailEntries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList()); + assertThat(propertyIds).containsExactly("num"); + + var entriesById = auditTrailEntries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x)); + assertThat(entriesById.get("num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getLogicalMemberIdentifier).isEqualTo("cmdexecauditsess.test.Counter#num")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPreValue).isNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getPostValue).isEqualTo("1")) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getInteractionId).isEqualTo(interaction.getInteractionId())) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getSequence).isEqualTo(0)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTarget).isEqualTo(target1)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getTimestamp).isNotNull()) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getType).isEqualTo(DomainChangeRecord.ChangeType.AUDIT_ENTRY)) + .satisfies(e -> assertThat(e).extracting(AuditTrailEntry::getUsername).isEqualTo("__system")); + } } @Test @@ -243,7 +233,6 @@ void invoke_direct() { // given var counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - { // when wrapperFactory.wrap(counter1).bumpUsingDeclaredAction(); @@ -270,29 +259,27 @@ void invoke_direct() { // ... but audit entries not yet generated var auditTrailEntries = auditTrailEntryRepository.findAll(); assertThat(auditTrailEntries).isEmpty(); - } // when - testSupport.nextInteraction(ia->{ // flushes the command and audit trail entries - - // then - // ... command entry now marked as complete - var commandLogEntries = commandLogEntryRepository.findAll(); - assertThat(commandLogEntries).hasSize(1); - var commandLogEntryAfter = commandLogEntries.get(0); - assertThat(commandLogEntryAfter) - .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) - .satisfies(e -> assertThat(e.getDuration()).isNotNull()) - .satisfies(e -> assertThat(e.getResult()).isNotNull()) - .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK")); - - if(!isJpa()) { - // and then - // ... audit trail entry created - var auditTrailEntries = auditTrailEntryRepository.findAll(); - assertThat(auditTrailEntries).hasSize(1); - } - }); + interactionService.nextInteraction(); // flushes the command and audit trail entries + + // then + // ... command entry now marked as complete + commandLogEntries = commandLogEntryRepository.findAll(); + assertThat(commandLogEntries).hasSize(1); + var commandLogEntryAfter = commandLogEntries.get(0); + assertThat(commandLogEntryAfter) + .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) + .satisfies(e -> assertThat(e.getDuration()).isNotNull()) + .satisfies(e -> assertThat(e.getResult()).isNotNull()) + .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK")); + + if(!isJpa()) { + // and then + // ... audit trail entry created + auditTrailEntries = auditTrailEntryRepository.findAll(); + assertThat(auditTrailEntries).hasSize(1); + } } @@ -301,7 +288,7 @@ void edit() { // given var counter1 = bookmarkService.lookup(target1, Counter.class).orElseThrow(); - { + // when wrapperFactory.wrap(counter1).setNum(99L); @@ -324,28 +311,27 @@ void edit() { // ... and audit entries not yet generated var auditTrailEntries = auditTrailEntryRepository.findAll(); assertThat(auditTrailEntries).isEmpty(); - } + // when - testSupport.nextInteraction(ia->{ // flushes the command and audit trail entries - - // then - // ... command entry now marked as complete - var commandLogEntries = commandLogEntryRepository.findAll(); - assertThat(commandLogEntries).hasSize(1); - var commandLogEntryAfter = commandLogEntries.get(0); - assertThat(commandLogEntryAfter) - .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) - .satisfies(e -> assertThat(e.getDuration()).isNotNull()) - .satisfies(e -> assertThat(e.getResult()).isNull()) // property edits are effectively void actions - .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK (VOID)")); - - if(!isJpa()) { - // and then - // ... audit trail entry created - var auditTrailEntries = auditTrailEntryRepository.findAll(); - assertThat(auditTrailEntries).hasSize(1); - } - }); + interactionService.nextInteraction(); // flushes the command and audit trail entries + + // then + // ... command entry now marked as complete + commandLogEntries = commandLogEntryRepository.findAll(); + assertThat(commandLogEntries).hasSize(1); + var commandLogEntryAfter = commandLogEntries.get(0); + assertThat(commandLogEntryAfter) + .satisfies(e -> assertThat(e.getCompletedAt()).isNotNull()) + .satisfies(e -> assertThat(e.getDuration()).isNotNull()) + .satisfies(e -> assertThat(e.getResult()).isNull()) // property edits are effectively void actions + .satisfies(e -> assertThat(e.getResultSummary()).isEqualTo("OK (VOID)")); + + if(!isJpa()) { + // and then + // ... audit trail entry created + auditTrailEntries = auditTrailEntryRepository.findAll(); + assertThat(auditTrailEntries).hasSize(1); + } } diff --git a/regressiontests/layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java b/regressiontests/layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java index eb26dc54a5b..17f1fdbac40 100644 --- a/regressiontests/layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java +++ b/regressiontests/layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java @@ -52,7 +52,6 @@ import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.iactnlayer.InteractionService; -import org.apache.causeway.applib.services.iactnlayer.InteractionService.TestSupport; import org.apache.causeway.applib.services.metamodel.MetaModelService; import org.apache.causeway.core.config.beans.CausewayBeanTypeRegistry; import org.apache.causeway.core.config.presets.CausewayPresets; @@ -124,15 +123,16 @@ static void beforeAll() { } Bookmark target1; - private TestSupport testSupport; @BeforeEach void beforeEach() { - this.testSupport = interactionService.testSupport(); - testSupport.nextInteraction(model->{ - Optional bookmark = bookmarkService.bookmarkFor(newCounter("counter-1")); - target1 = bookmark.orElseThrow(); - }); + interactionService.nextInteraction(); + + Optional bookmark = bookmarkService.bookmarkFor(newCounter("counter-1")); + target1 = bookmark.orElseThrow(); + + interactionService.nextInteraction(); + } protected Counter newCounter(final String name) { From 135972c0c57d87de2691b58b59ed00e2f442005b Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sun, 22 Mar 2026 10:36:56 +0100 Subject: [PATCH 13/15] CAUSEWAY-3975: adds default OpenTelemetryServerRequestObservationConvention --- commons/src/main/java/module-info.java | 1 + .../CausewayObservationInternal.java | 11 +++++-- .../session/InteractionServiceDefault.java | 9 ++++-- .../core/webapp/CausewayModuleCoreWebapp.java | 30 +++++++++++++++++-- .../CausewayModuleViewerWicketViewer.java | 4 +-- .../integration/TelemetryStartHandler.java | 8 ++++- .../wicketapp/CausewayWicketApplication.java | 4 +-- 7 files changed, 54 insertions(+), 13 deletions(-) diff --git a/commons/src/main/java/module-info.java b/commons/src/main/java/module-info.java index a5d7963e405..47a24ab13b0 100644 --- a/commons/src/main/java/module-info.java +++ b/commons/src/main/java/module-info.java @@ -68,6 +68,7 @@ requires transitive tools.jackson.core; requires transitive tools.jackson.databind; requires transitive tools.jackson.module.jakarta.xmlbind; + requires transitive micrometer.commons; requires transitive micrometer.observation; requires transitive org.jdom2; requires transitive org.jspecify; diff --git a/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java b/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java index 2d7ea230a6b..7248a116165 100644 --- a/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java +++ b/commons/src/main/java/org/apache/causeway/commons/internal/observation/CausewayObservationInternal.java @@ -28,6 +28,7 @@ import lombok.Data; import lombok.experimental.Accessors; +import io.micrometer.common.KeyValue; import io.micrometer.observation.Observation; import io.micrometer.observation.Observation.Scope; import io.micrometer.observation.ObservationRegistry; @@ -68,7 +69,7 @@ public boolean isNoop() { public Observation createNotStarted(final Class bean, final String name) { return Observation.createNotStarted(name, observationRegistry) .lowCardinalityKeyValue("module", module) - .highCardinalityKeyValue("bean", bean.getSimpleName()); + .lowCardinalityKeyValue("bean", bean.getSimpleName()); } @FunctionalInterface @@ -84,7 +85,7 @@ public ObservationProvider provider(final Class bean) { * Helps if start and stop of an {@link Observation} happen in different code locations. */ @Data @Accessors(fluent = true) - public static class ObservationClosure implements AutoCloseable { + public static final class ObservationClosure implements AutoCloseable { private Observation observation; private Scope scope; @@ -123,4 +124,10 @@ public ObservationClosure tag(final String key, @Nullable final Supplier } + public static KeyValue currentThreadId() { + var ct = Thread.currentThread(); + return KeyValue.of("threadId", "%d [%s]".formatted(ct.getId(), ct.getName())); + + } + } diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java index 841f5f582db..ca90c91139a 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java @@ -147,11 +147,16 @@ public InteractionLayer openInteraction(final @NonNull InteractionContext intera .map(it->(CausewayInteraction)it) .orElseGet(()->new CausewayInteraction(interactionIdGenerator.interactionId())); - var obs = observationProvider.get("Causeway Layered Interaction") - .highCardinalityKeyValue("stackSize", ""+getInteractionLayerCount()); + var obs = observationProvider.get(getInteractionLayerCount()==0 + ? "Causeway Root Interaction" + : "Causeway Nested Interaction"); var newInteractionLayer = layerStack.push(causewayInteraction, interactionContextToUse, obs); + if(getInteractionLayerCount()>0) { + obs.highCardinalityKeyValue("stackedLayers", ""+getInteractionLayerCount()); + } + if(isAtTopLevel()) { transactionServiceSpring.onOpen(causewayInteraction); interactionScopeLifecycleHandler.onTopLevelInteractionOpened(); diff --git a/core/webapp/src/main/java/org/apache/causeway/core/webapp/CausewayModuleCoreWebapp.java b/core/webapp/src/main/java/org/apache/causeway/core/webapp/CausewayModuleCoreWebapp.java index fa99d8e5de6..6a16a0b7739 100644 --- a/core/webapp/src/main/java/org/apache/causeway/core/webapp/CausewayModuleCoreWebapp.java +++ b/core/webapp/src/main/java/org/apache/causeway/core/webapp/CausewayModuleCoreWebapp.java @@ -23,9 +23,12 @@ import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention; +import org.springframework.http.server.observation.ServerRequestObservationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextListener; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; import org.apache.causeway.core.interaction.session.MessageBrokerImpl; import org.apache.causeway.core.metamodel.services.message.MessageBroker; import org.apache.causeway.core.runtime.CausewayModuleCoreRuntime; @@ -36,7 +39,9 @@ import org.apache.causeway.core.webapp.modules.templresources.WebModuleTemplateResources; import org.apache.causeway.core.webapp.webappctx.CausewayWebAppContextInitializer; -@Configuration +import io.micrometer.common.KeyValues; + +@Configuration(proxyBeanMethods = false) @Import({ // Modules CausewayModuleCoreRuntime.class, @@ -61,7 +66,7 @@ public class CausewayModuleCoreWebapp { @Scope( value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) - public MessageBroker sessionScopedMessageBroker() { + MessageBroker sessionScopedMessageBroker() { return new MessageBrokerImpl(); } @@ -73,8 +78,27 @@ public MessageBroker sessionScopedMessageBroker() { * @see https://stackoverflow.com/a/61431621/56880 */ @Bean - public RequestContextListener requestContextListener() { + RequestContextListener requestContextListener() { return new RequestContextListener(); } + /** + * https://docs.spring.io/spring-boot/reference/actuator/observability.html + */ + @Bean + OpenTelemetryServerRequestObservationConvention openTelemetryServerRequestObservationConvention() { + return new OpenTelemetryServerRequestObservationConvention() { + @Override + public String getContextualName(final ServerRequestObservationContext context) { + return super.getContextualName(context) + " {TODO}"; + } + @Override + public KeyValues getHighCardinalityKeyValues(final ServerRequestObservationContext context) { + // Make sure that KeyValues entries are already sorted by name for better performance + return KeyValues.of(methodOriginal(context), httpUrl(context), + CausewayObservationInternal.currentThreadId()); + } + }; + } + } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/CausewayModuleViewerWicketViewer.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/CausewayModuleViewerWicketViewer.java index 8746f572e7c..b3482c5feb3 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/CausewayModuleViewerWicketViewer.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/CausewayModuleViewerWicketViewer.java @@ -43,7 +43,7 @@ /** * @since 1.x {@index} */ -@Configuration +@Configuration(proxyBeanMethods = false) @Import({ // Modules CausewayModuleViewerWicketUi.class, @@ -69,9 +69,9 @@ PageClassRegistryDefault.AutoConfiguration.class, PageNavigationServiceDefault.AutoConfiguration.class, HintStoreUsingWicketSession.AutoConfiguration.class, - }) public class CausewayModuleViewerWicketViewer { public static final String NAMESPACE = "causeway.viewer.wicket"; + } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java index daeef157a43..41504896e40 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java @@ -22,6 +22,7 @@ import org.apache.wicket.request.cycle.IRequestCycleListener; import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; import org.apache.causeway.commons.internal.observation.CausewayObservationInternal.ObservationProvider; /** @@ -31,11 +32,16 @@ public record TelemetryStartHandler( ObservationProvider observationProvider) implements IRequestCycleListener { + public TelemetryStartHandler(final CausewayObservationInternal observationInternal) { + this(observationInternal.provider(TelemetryStartHandler.class)); + } + @Override public synchronized void onBeginRequest(final RequestCycle requestCycle) { if (requestCycle instanceof RequestCycle2 requestCycle2) { requestCycle2.observationClosure.startAndOpenScope( - observationProvider.get("Apache Wicket Request Cycle")); + observationProvider.get("Apache Wicket Request Cycle") + .lowCardinalityKeyValue("ck2", "test2")); } } diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java index 597e9548e82..36985ca8661 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/wicketapp/CausewayWicketApplication.java @@ -21,7 +21,6 @@ import java.time.Duration; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.UUID; import java.util.function.Function; @@ -213,8 +212,7 @@ protected void init() { getRequestCycleSettings().setRenderStrategy(RequestCycleSettings.RenderStrategy.REDIRECT_TO_RENDER); getResourceSettings().setParentFolderPlaceholder("$up$"); - getRequestCycleListeners().add(new TelemetryStartHandler(Objects.requireNonNull(observationInternal) - .provider(TelemetryStartHandler.class))); + getRequestCycleListeners().add(new TelemetryStartHandler(observationInternal)); getRequestCycleListeners().add(new WebRequestCycleForCauseway(metaModelContext, getPageClassRegistry())); getRequestCycleListeners().add(new TelemetryStopHandler(metricService)); getRequestCycleListeners().add(new RehydrationHandler()); From c338b82a8a8cad95c6f7f94b6db651882dabf5fe Mon Sep 17 00:00:00 2001 From: andi-huber Date: Sun, 22 Mar 2026 11:16:22 +0100 Subject: [PATCH 14/15] CAUSEWAY-3975: adds request path to top level observation name --- .../causeway/core/webapp/CausewayModuleCoreWebapp.java | 5 ++++- .../wicket/viewer/integration/TelemetryStartHandler.java | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/webapp/src/main/java/org/apache/causeway/core/webapp/CausewayModuleCoreWebapp.java b/core/webapp/src/main/java/org/apache/causeway/core/webapp/CausewayModuleCoreWebapp.java index 6a16a0b7739..f9e61895a32 100644 --- a/core/webapp/src/main/java/org/apache/causeway/core/webapp/CausewayModuleCoreWebapp.java +++ b/core/webapp/src/main/java/org/apache/causeway/core/webapp/CausewayModuleCoreWebapp.java @@ -28,6 +28,7 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextListener; +import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.commons.internal.observation.CausewayObservationInternal; import org.apache.causeway.core.interaction.session.MessageBrokerImpl; import org.apache.causeway.core.metamodel.services.message.MessageBroker; @@ -90,7 +91,9 @@ OpenTelemetryServerRequestObservationConvention openTelemetryServerRequestObserv return new OpenTelemetryServerRequestObservationConvention() { @Override public String getContextualName(final ServerRequestObservationContext context) { - return super.getContextualName(context) + " {TODO}"; + return "%s (%s)".formatted( + super.getContextualName(context), + _Strings.ellipsifyAtEnd(context.getCarrier().getRequestURI(), 80, "...")); } @Override public KeyValues getHighCardinalityKeyValues(final ServerRequestObservationContext context) { diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java index 41504896e40..1d5cda6ada8 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/integration/TelemetryStartHandler.java @@ -40,8 +40,7 @@ public TelemetryStartHandler(final CausewayObservationInternal observationIntern public synchronized void onBeginRequest(final RequestCycle requestCycle) { if (requestCycle instanceof RequestCycle2 requestCycle2) { requestCycle2.observationClosure.startAndOpenScope( - observationProvider.get("Apache Wicket Request Cycle") - .lowCardinalityKeyValue("ck2", "test2")); + observationProvider.get("Apache Wicket Request Cycle")); } } From 48f67336b502202ce7f9f526dc169eca72231719 Mon Sep 17 00:00:00 2001 From: andi-huber Date: Mon, 23 Mar 2026 09:26:39 +0100 Subject: [PATCH 15/15] CAUSEWAY-3975: on interaction start provide user info --- .../causeway/commons/having/HasTypeSpecificAttributes.java | 5 +++++ .../core/interaction/session/CausewayInteraction.java | 4 ++-- .../runtimeservices/session/InteractionServiceDefault.java | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/commons/src/main/java/org/apache/causeway/commons/having/HasTypeSpecificAttributes.java b/commons/src/main/java/org/apache/causeway/commons/having/HasTypeSpecificAttributes.java index 5e4216112d3..c4563b5445a 100644 --- a/commons/src/main/java/org/apache/causeway/commons/having/HasTypeSpecificAttributes.java +++ b/commons/src/main/java/org/apache/causeway/commons/having/HasTypeSpecificAttributes.java @@ -18,6 +18,7 @@ */ package org.apache.causeway.commons.having; +import java.util.Optional; import java.util.function.Function; public interface HasTypeSpecificAttributes { @@ -31,6 +32,10 @@ public interface HasTypeSpecificAttributes { /** get type specific attribute */ T getAttribute(Class type); + default Optional lookupAttribute(final Class type) { + return Optional.ofNullable(getAttribute(type)); + } + /** remove type specific attribute */ void removeAttribute(Class type); diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/session/CausewayInteraction.java b/core/interaction/src/main/java/org/apache/causeway/core/interaction/session/CausewayInteraction.java index 12b91d874ef..f1c3148f307 100644 --- a/core/interaction/src/main/java/org/apache/causeway/core/interaction/session/CausewayInteraction.java +++ b/core/interaction/src/main/java/org/apache/causeway/core/interaction/session/CausewayInteraction.java @@ -37,6 +37,7 @@ import org.apache.causeway.commons.internal.collections._Lists; import org.apache.causeway.commons.internal.exceptions._Exceptions; import org.apache.causeway.core.metamodel.execution.InteractionInternal; + import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -212,9 +213,8 @@ private Execution popAndComplete( final ClockService clockService, final MetricsService metricsService) { - if(currentExecution == null) { + if(currentExecution == null) throw new IllegalStateException("No current execution to pop"); - } final Execution popped = currentExecution; var completedAt = clockService.getClock().nowAsJavaSqlTimestamp(); diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java index ca90c91139a..dbd88e2f468 100644 --- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/session/InteractionServiceDefault.java @@ -47,6 +47,7 @@ import org.apache.causeway.applib.services.iactnlayer.InteractionService; import org.apache.causeway.applib.services.inject.ServiceInjector; import org.apache.causeway.commons.functional.ThrowingRunnable; +import org.apache.causeway.commons.internal.base._Strings; import org.apache.causeway.commons.internal.debug._Probe; import org.apache.causeway.commons.internal.debug.xray.XrayUi; import org.apache.causeway.commons.internal.exceptions._Exceptions; @@ -153,6 +154,11 @@ public InteractionLayer openInteraction(final @NonNull InteractionContext intera : "Causeway Nested Interaction"); var newInteractionLayer = layerStack.push(causewayInteraction, interactionContextToUse, obs); + obs.highCardinalityKeyValue("user.isImpersonating", "" + interactionContextToUse.getUser().isImpersonating()); + _Strings.nonEmpty(interactionContextToUse.getUser().multiTenancyToken()) + .ifPresent(value->obs.highCardinalityKeyValue("user.multiTenancyToken", value)); + _Strings.nonEmpty(interactionContextToUse.getUser().name()) + .ifPresent(value->obs.highCardinalityKeyValue("user.name", value)); if(getInteractionLayerCount()>0) { obs.highCardinalityKeyValue("stackedLayers", ""+getInteractionLayerCount()); }