This is an automated email from the ASF dual-hosted git repository. danhaywood pushed a commit to branch ISIS-3110 in repository https://gitbox.apache.org/repos/asf/isis.git
commit 0dfc3b6a0352c6c72cce1f4b8de9462139cac284 Author: Dan Haywood <[email protected]> AuthorDate: Thu Aug 4 09:30:13 2022 +0100 ISIS-3110: moves the subscriber for EntityTrackerChangeDefault... ... into a singleton that can then actively check that there is an interaction in scope --- ...ctionInvocationFacetForDomainEventAbstract.java | 8 +-- .../autocomplete/AutoCompleteFacetAbstract.java | 4 +- .../services/publishing/ExecutionPublisher.java | 4 +- .../specimpl/OneToManyAssociationMixedIn.java | 4 +- .../specimpl/OneToOneAssociationMixedIn.java | 4 +- .../executor/MemberExecutorServiceDefault.java | 22 ++++-- .../publish/EntityChangesPublisherDefault.java | 2 +- .../EntityPropertyChangePublisherDefault.java | 4 +- .../publish/ExecutionPublisherDefault.java | 11 ++- .../publish/ObjectLifecyclePublisherDefault.java | 16 ++--- .../changetracking/EntityChangeTracker.java | 15 +++- .../changetracking/EntityChangesPublisher.java | 2 +- .../HasInteractionId_commandLogEntry.java | 5 +- .../ApplicationPermissionRepositoryAbstract.java | 4 +- .../dom/ApplicationRoleRepositoryAbstract.java | 3 +- .../dom/ApplicationTenancyRepositoryAbstract.java | 2 +- .../dom/ApplicationUserRepositoryAbstract.java | 5 +- .../secman/applib/user/menu/MeService.java | 3 +- .../integration/authorizor/AuthorizorSecman.java | 10 +-- .../facets/TenantedAuthorizationFacetDefault.java | 4 +- .../facets/TenantedAuthorizationPostProcessor.java | 1 + .../commons/IsisModulePersistenceCommons.java | 1 + .../changetracking/EntityChangeTrackerDefault.java | 79 +++++++++++++++++++--- .../IsisModulePersistenceJdoDatanucleus.java | 10 +-- .../changetracking/JdoLifecycleListener.java | 9 ++- .../persistence/jpa/JpaBootstrappingTest.java | 2 +- ...rgetRespondListenerToResetQueryResultCache.java | 3 +- 27 files changed, 165 insertions(+), 72 deletions(-) diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java index 36efa2a4ed..2be7bf26d8 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java @@ -152,7 +152,7 @@ implements ImperativeFacet { final ActionSemanticsFacet semanticsFacet = getFacetHolder().getFacet(ActionSemanticsFacet.class); final boolean cacheable = semanticsFacet != null && semanticsFacet.value().isSafeAndRequestCacheable(); if(cacheable) { - final QueryResultsCache queryResultsCache = getQueryResultsCache(); + final QueryResultsCache queryResultsCache = queryResultsCache(); final Object[] targetPojoPlusExecutionParameters = _Arrays.combine(executionParameters, targetPojo); return queryResultsCache.execute( ()->CanonicalInvoker.invoke(method, targetPojo, executionParameters), @@ -163,11 +163,11 @@ implements ImperativeFacet { } } - private QueryResultsCache getQueryResultsCache() { + private QueryResultsCache queryResultsCache() { return serviceRegistry.lookupServiceElseFail(QueryResultsCache.class); } - private InteractionDtoFactory getInteractionDtoServiceInternal() { + private InteractionDtoFactory interactionDtoFactory() { return serviceRegistry.lookupServiceElseFail(InteractionDtoFactory.class); } @@ -184,7 +184,7 @@ implements ImperativeFacet { public Object execute(final ActionInvocation currentExecution) { // update the current execution with the DTO (memento) - val invocationDto = getInteractionDtoServiceInternal() + val invocationDto = interactionDtoFactory() .asActionInvocationDto(owningAction, head, initialArgs); currentExecution.setDto(invocationDto); diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/autocomplete/AutoCompleteFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/autocomplete/AutoCompleteFacetAbstract.java index e5a31b73bb..5cca5267fe 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/autocomplete/AutoCompleteFacetAbstract.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/autocomplete/AutoCompleteFacetAbstract.java @@ -73,7 +73,7 @@ implements AutoCompleteFacet { final String search, final InteractionInitiatedBy interactionInitiatedBy) { - val resultAdapter = getPublisherDispatchService() + val resultAdapter = executionPublisher() .withPublishingSuppressed(()->{ final Object list = _Reflect.invokeMethodOn(repositoryMethod, getRepository(), search) .ifFailure(e->log.warn("failure while executing auto-complete", e)) @@ -90,7 +90,7 @@ implements AutoCompleteFacet { return getServiceRegistry().lookupService(repositoryClass).orElse(null); } - private ExecutionPublisher getPublisherDispatchService() { + private ExecutionPublisher executionPublisher() { return getServiceRegistry().lookupServiceElseFail(ExecutionPublisher.class); } diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/ExecutionPublisher.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/ExecutionPublisher.java index 197e043ca9..4007aace35 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/ExecutionPublisher.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/ExecutionPublisher.java @@ -20,6 +20,8 @@ package org.apache.isis.core.metamodel.services.publishing; import java.util.function.Supplier; +import org.springframework.beans.factory.DisposableBean; + import org.apache.isis.applib.annotation.Action; import org.apache.isis.applib.annotation.Property; import org.apache.isis.applib.services.iactn.Execution; @@ -33,7 +35,7 @@ import org.apache.isis.applib.services.publishing.spi.ExecutionSubscriber; * * @see ExecutionSubscriber */ -public interface ExecutionPublisher { +public interface ExecutionPublisher extends DisposableBean { /** * Notifies {@link ExecutionSubscriber}s of an action invocation through diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java index a216d1e76e..911fabc6a6 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java @@ -145,7 +145,7 @@ implements MixedInMember { final ManagedObject ownerAdapter, final InteractionInitiatedBy interactionInitiatedBy) { - return getPublishingServiceInternal().withPublishingSuppressed( + return executionPublisher().withPublishingSuppressed( () -> mixinAction.executeInternal( headFor(ownerAdapter), Can.empty(), interactionInitiatedBy)); } @@ -176,7 +176,7 @@ implements MixedInMember { || _Annotations.synthesize(javaMethod, Domain.Include.class).isPresent(); } - private ExecutionPublisher getPublishingServiceInternal() { + private ExecutionPublisher executionPublisher() { return getServiceRegistry().lookupServiceElseFail(ExecutionPublisher.class); } diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java index 8618c0de79..d90bfc5cf3 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java @@ -128,7 +128,7 @@ implements MixedInMember { val head = headFor(mixedInAdapter); - return getPublisherDispatchService().withPublishingSuppressed( + return executionPublisher().withPublishingSuppressed( () -> mixinAction.executeInternal(head, Can.empty(), interactionInitiatedBy) ); } @@ -159,7 +159,7 @@ implements MixedInMember { || _Annotations.synthesize(javaMethod, Domain.Include.class).isPresent(); } - private ExecutionPublisher getPublisherDispatchService() { + private ExecutionPublisher executionPublisher() { return getServiceRegistry().lookupServiceElseFail(ExecutionPublisher.class); } diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java index 4978fd27d2..2768bb108b 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java @@ -51,7 +51,6 @@ import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; import org.apache.isis.core.metamodel.execution.InteractionInternal; import org.apache.isis.core.metamodel.execution.MemberExecutorService; import org.apache.isis.core.metamodel.facetapi.FacetHolder; -import org.apache.isis.core.metamodel.facets.actions.action.invocation.IdentifierUtil; import org.apache.isis.core.metamodel.facets.members.publish.command.CommandPublishingFacet; import org.apache.isis.core.metamodel.facets.members.publish.execution.ExecutionPublishingFacet; import org.apache.isis.core.metamodel.facets.properties.property.modify.PropertySetterOrClearFacetForDomainEventAbstract.EditingVariant; @@ -91,12 +90,20 @@ implements MemberExecutorService { private final @Getter ObjectManager objectManager; private final @Getter ClockService clockService; private final @Getter ServiceInjector serviceInjector; - private final @Getter Provider<MetricsService> metricsService; + private final @Getter Provider<MetricsService> metricsServiceProvider; private final @Getter InteractionDtoFactory interactionDtoFactory; - private final @Getter Provider<ExecutionPublisher> executionPublisher; + private final @Getter Provider<ExecutionPublisher> executionPublisherProvider; private final @Getter MetamodelEventService metamodelEventService; private final @Getter TransactionService transactionService; + private MetricsService metricsService() { + return metricsServiceProvider.get(); + } + + private ExecutionPublisher executionPublisher() { + return executionPublisherProvider.get(); + } + @Override public Optional<InteractionInternal> getInteraction() { return interactionLayerTracker.currentInteraction() @@ -150,7 +157,7 @@ implements MemberExecutorService { val memberExecutor = actionExecutorFactory.createExecutor(owningAction, head, argumentAdapters); // sets up startedAt and completedAt on the execution, also manages the execution call graph - interaction.execute(memberExecutor, actionInvocation, clockService, metricsService.get(), command); + interaction.execute(memberExecutor, actionInvocation, clockService, metricsService(), command); // handle any exceptions final Execution<ActionInvocationDto, ?> priorExecution = @@ -179,13 +186,14 @@ implements MemberExecutorService { // publish (if not a contributed association, query-only mixin) if (ExecutionPublishingFacet.isPublishingEnabled(facetHolder)) { - executionPublisher.get().publishActionInvocation(priorExecution); + executionPublisher().publishActionInvocation(priorExecution); } val result = resultFilteredHonoringVisibility(method, returnedAdapter, interactionInitiatedBy); _Xray.exitInvocation(xrayHandle); return result; } + @Override public ManagedObject setOrClearProperty( final @NonNull OneToOneAssociation owningProperty, @@ -218,7 +226,7 @@ implements MemberExecutorService { interactionInitiatedBy, editingVariant); // sets up startedAt and completedAt on the execution, also manages the execution call graph - val targetPojo = interaction.execute(executor, propertyEdit, clockService, metricsService.get(), command); + val targetPojo = interaction.execute(executor, propertyEdit, clockService, metricsService(), command); // handle any exceptions final Execution<?, ?> priorExecution = interaction.getPriorExecution(); @@ -235,7 +243,7 @@ implements MemberExecutorService { // publish (if not a contributed association, query-only mixin) val publishedPropertyFacet = facetHolder.getFacet(ExecutionPublishingFacet.class); if (publishedPropertyFacet != null) { - executionPublisher.get().publishPropertyEdit(priorExecution); + executionPublisher().publishPropertyEdit(priorExecution); } val result = getObjectManager().adapt(targetPojo); diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityChangesPublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityChangesPublisherDefault.java index 69b0b251e1..883f5cb60c 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityChangesPublisherDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityChangesPublisherDefault.java @@ -85,7 +85,7 @@ public class EntityChangesPublisherDefault implements EntityChangesPublisher { // -- HELPER - private Optional<EntityChanges> getPayload(HasEnlistedEntityChanges hasEnlistedEntityChanges) { + private Optional<EntityChanges> getPayload(final @NonNull HasEnlistedEntityChanges hasEnlistedEntityChanges) { return enabledSubscribers.isEmpty() ? Optional.empty() : hasEnlistedEntityChanges.getEntityChanges( diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java index 83e4cda4e2..c83f1406e7 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java @@ -72,7 +72,7 @@ public class EntityPropertyChangePublisherDefault implements EntityPropertyChang .filter(HasEnabling::isEnabled); } - private HasEnlistedEntityPropertyChanges getHasEnlistedEntityPropertyChanges() { + private HasEnlistedEntityPropertyChanges hasEnlistedEntityPropertyChanges() { return hasEnlistedEntityPropertyChangesProvider.get(); } @@ -89,7 +89,7 @@ public class EntityPropertyChangePublisherDefault implements EntityPropertyChang val currentUser = userService.currentUserNameElseNobody(); val currentTransactionId = transactionService.currentTransactionId().orElse(TransactionId.empty()); - val propertyChanges = getHasEnlistedEntityPropertyChanges().getPropertyChanges( + val propertyChanges = hasEnlistedEntityPropertyChanges().getPropertyChanges( currentTime, currentUser, currentTransactionId); diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ExecutionPublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ExecutionPublisherDefault.java index 62fe2a68f4..c8e641a8b5 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ExecutionPublisherDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ExecutionPublisherDefault.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.concurrent.atomic.LongAdder; import java.util.function.Supplier; +import org.springframework.beans.factory.DisposableBean; import org.springframework.lang.Nullable; import javax.annotation.PostConstruct; import javax.annotation.Priority; @@ -57,6 +58,10 @@ implements ExecutionPublisher { private final InteractionLayerTracker iaTracker; private Can<ExecutionSubscriber> enabledSubscribers = Can.empty(); + /** + * this is the reason that this service is @InteractionScope'd + */ + private final LongAdder suppressionRequestCounter = new LongAdder(); @PostConstruct public void init() { @@ -64,6 +69,11 @@ implements ExecutionPublisher { .filter(HasEnabling::isEnabled); } + @Override + public void destroy() throws Exception { + suppressionRequestCounter.reset(); + } + @Override public void publishActionInvocation(final Execution<?,?> execution) { notifySubscribers(execution); @@ -104,7 +114,6 @@ implements ExecutionPublisher { } - private final LongAdder suppressionRequestCounter = new LongAdder(); private boolean canPublish() { return enabledSubscribers.isNotEmpty() diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java index 26feea1bfc..71489accdb 100644 --- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java +++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java @@ -28,6 +28,7 @@ import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; import org.apache.isis.applib.annotation.PriorityPrecedence; +import org.apache.isis.applib.services.iactnlayer.InteractionService; import org.apache.isis.commons.collections.Can; import org.apache.isis.core.metamodel.facets.object.callbacks.CallbackFacet; import org.apache.isis.core.metamodel.facets.object.callbacks.LoadedCallbackFacet; @@ -46,6 +47,8 @@ import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices; import org.apache.isis.core.transaction.changetracking.EntityChangeTracker; import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent; +import lombok.RequiredArgsConstructor; + /** * @see ObjectLifecyclePublisher * @since 2.0 {@index} @@ -54,23 +57,18 @@ import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent; @Named(IsisModuleCoreRuntimeServices.NAMESPACE + ".ObjectLifecyclePublisherDefault") @Priority(PriorityPrecedence.EARLY) @Qualifier("Default") +@RequiredArgsConstructor(onConstructor_ = {@Inject}) //@Log4j2 public class ObjectLifecyclePublisherDefault implements ObjectLifecyclePublisher { private final Provider<EntityChangeTracker> entityChangeTrackerProvider; private final Provider<LifecycleCallbackNotifier> lifecycleCallbackNotifierProvider; - - @Inject - public ObjectLifecyclePublisherDefault( - final Provider<EntityChangeTracker> entityChangeTrackerProvider, - final Provider<LifecycleCallbackNotifier> lifecycleCallbackNotifierProvider) { - this.entityChangeTrackerProvider = entityChangeTrackerProvider; - this.lifecycleCallbackNotifierProvider = lifecycleCallbackNotifierProvider; - } + private final InteractionService interactionService; EntityChangeTracker entityChangeTracker() { - return entityChangeTrackerProvider.get(); + return interactionService.isInInteraction() ? entityChangeTrackerProvider.get() : EntityChangeTracker.NOOP; } + LifecycleCallbackNotifier lifecycleCallbackNotifier() { return lifecycleCallbackNotifierProvider.get(); } diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java index eaf09281b4..a75a7ead10 100644 --- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java +++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java @@ -18,6 +18,7 @@ */ package org.apache.isis.core.transaction.changetracking; +import org.springframework.beans.factory.DisposableBean; import org.springframework.lang.Nullable; import org.apache.isis.commons.collections.Can; @@ -30,7 +31,19 @@ import org.apache.isis.core.metamodel.spec.ManagedObject; * * @since 1.x but renamed/refactored for v2 {@index} */ -public interface EntityChangeTracker { +public interface EntityChangeTracker extends DisposableBean { + + /** + * Provided primarily for testing, but also used in cases where an attempt is made to resolve a bean but + * there is no active interaction. + */ + EntityChangeTracker NOOP = new EntityChangeTracker() { + @Override public void destroy() throws Exception {} + @Override public void enlistCreated(ManagedObject entity) {} + @Override public void enlistUpdating(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {} + @Override public void enlistDeleting(ManagedObject entity) {} + @Override public void incrementLoaded(ManagedObject entity) {} + }; /** * Publishing support: for object stores to enlist an object that has just been created, diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangesPublisher.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangesPublisher.java index d3c6efec0e..a5583acd70 100644 --- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangesPublisher.java +++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangesPublisher.java @@ -33,6 +33,6 @@ public interface EntityChangesPublisher { * an {@link org.apache.isis.applib.services.iactn.Interaction}, calling * the {@link EntityChangesSubscriber#onChanging(EntityChanges)} callback. */ - void publishChangingEntities(HasEnlistedEntityChanges hasEnlistedEntityChanges); + void publishChangingEntities(final HasEnlistedEntityChanges hasEnlistedEntityChanges); } diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/contributions/HasInteractionId_commandLogEntry.java b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/contributions/HasInteractionId_commandLogEntry.java index 2245fb3420..9961cb3ac7 100644 --- a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/contributions/HasInteractionId_commandLogEntry.java +++ b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/contributions/HasInteractionId_commandLogEntry.java @@ -21,6 +21,7 @@ package org.apache.isis.extensions.commandlog.applib.contributions; import javax.inject.Inject; +import javax.inject.Provider; import org.apache.isis.applib.annotation.Property; import org.apache.isis.applib.mixins.system.HasInteractionId; @@ -48,7 +49,7 @@ public class HasInteractionId_commandLogEntry { public CommandLogEntry prop() { - return queryResultsCache.execute(this::doProp, getClass(), "prop"); + return queryResultsCacheProvider.get().execute(this::doProp, getClass(), "prop"); } private CommandLogEntry doProp() { @@ -64,6 +65,6 @@ public class HasInteractionId_commandLogEntry { } @Inject CommandLogEntryRepository<? extends CommandLogEntry> commandLogEntryRepository; - @Inject QueryResultsCache queryResultsCache; + @Inject Provider<QueryResultsCache> queryResultsCacheProvider; } diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/permission/dom/ApplicationPermissionRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/permission/dom/ApplicationPermissionRepositoryAbstract.java index 4035605680..0f94b8ab4a 100644 --- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/permission/dom/ApplicationPermissionRepositoryAbstract.java +++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/permission/dom/ApplicationPermissionRepositoryAbstract.java @@ -24,6 +24,7 @@ import java.util.Optional; import java.util.stream.Collectors; import javax.inject.Inject; +import javax.inject.Provider; import org.apache.isis.applib.query.Query; import org.apache.isis.applib.services.appfeat.ApplicationFeature; @@ -54,11 +55,10 @@ implements ApplicationPermissionRepository { @Inject private RepositoryService repository; @Inject private ApplicationFeatureRepository featureRepository; - @Inject private ApplicationRoleRepository roleRepository; @Inject private FactoryService factory; @Inject private MessageService messages; - @Inject private javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider; + @Inject private Provider<QueryResultsCache> queryResultsCacheProvider; private final Class<P> applicationPermissionClass; diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/dom/ApplicationRoleRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/dom/ApplicationRoleRepositoryAbstract.java index b23ab86855..59d7d2f902 100644 --- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/dom/ApplicationRoleRepositoryAbstract.java +++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/dom/ApplicationRoleRepositoryAbstract.java @@ -51,8 +51,7 @@ implements ApplicationRoleRepository { @Inject private FactoryService factoryService; @Inject private RepositoryService repository; @Inject private IsisConfiguration config; - @Inject RegexReplacer regexReplacer; - + @Inject private RegexReplacer regexReplacer; @Inject private Provider<QueryResultsCache> queryResultsCacheProvider; private final Class<R> applicationRoleClass; diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/tenancy/dom/ApplicationTenancyRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/tenancy/dom/ApplicationTenancyRepositoryAbstract.java index bc441a40a4..502e189012 100644 --- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/tenancy/dom/ApplicationTenancyRepositoryAbstract.java +++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/tenancy/dom/ApplicationTenancyRepositoryAbstract.java @@ -40,8 +40,8 @@ implements ApplicationTenancyRepository { @Inject private FactoryService factory; @Inject private RepositoryService repository; + @Inject private RegexReplacer regexReplacer; @Inject private Provider<QueryResultsCache> queryResultsCacheProvider; - @Inject RegexReplacer regexReplacer; private final Class<T> applicationTenancyClass; diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java index a0f6c56381..e046776c3e 100644 --- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java +++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java @@ -25,6 +25,7 @@ import java.util.Optional; import java.util.function.Consumer; import javax.inject.Inject; +import javax.inject.Provider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -57,12 +58,12 @@ implements ApplicationUserRepository { @Inject private RepositoryService repository; @Inject protected IsisConfiguration config; @Inject private EventBusService eventBusService; - @Inject RegexReplacer regexReplacer; + @Inject private RegexReplacer regexReplacer; + @Inject private Provider<QueryResultsCache> queryResultsCacheProvider; // empty if no candidate is available @Autowired(required = false) @Qualifier("secman") PasswordEncoder passwordEncoder; - @Inject private javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider; private final Class<U> applicationUserClass; diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/menu/MeService.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/menu/MeService.java index 3611c45dff..18a0bcdfe5 100644 --- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/menu/MeService.java +++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/menu/MeService.java @@ -22,6 +22,7 @@ import java.util.concurrent.Callable; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Provider; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @@ -68,7 +69,7 @@ public class MeService { final ApplicationUserRepository applicationUserRepository; final UserService userService; - final javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider; + final Provider<QueryResultsCache> queryResultsCacheProvider; @ObjectSupport public String iconName() { diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authorizor/AuthorizorSecman.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authorizor/AuthorizorSecman.java index 8b4bfa41b4..95e1fd4c8e 100644 --- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authorizor/AuthorizorSecman.java +++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authorizor/AuthorizorSecman.java @@ -95,21 +95,17 @@ public class AuthorizorSecman implements Authorizor { @InteractionScope static class PermissionCache implements DisposableBean { - private Map<String, Optional<ApplicationPermissionValueSet>> permissionsByUsername; + private final Map<String, Optional<ApplicationPermissionValueSet>> permissionsByUsername = _Maps.newHashMap(); @Override - public void destroy() throws Exception { - permissionsByUsername = null; + public void destroy() { + permissionsByUsername.clear(); } Optional<ApplicationPermissionValueSet> computeIfAbsent( final @NonNull String userName, final Supplier<Optional<ApplicationPermissionValueSet>> lookup) { - if(permissionsByUsername==null) { - permissionsByUsername = _Maps.newHashMap(); - } - return permissionsByUsername.computeIfAbsent(userName, __->lookup.get()); } diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java index a86191527d..2b2a1dd19e 100644 --- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java +++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java @@ -37,14 +37,14 @@ public class TenantedAuthorizationFacetDefault extends FacetAbstract implements TenantedAuthorizationFacet { - private static final Class<? extends Facet> type() { + private static Class<? extends Facet> type() { return TenantedAuthorizationFacet.class; } private final List<ApplicationTenancyEvaluator> evaluators; private final ApplicationUserRepository applicationUserRepository; - private final Provider<QueryResultsCache> queryResultsCacheProvider; private final UserService userService; + private final Provider<QueryResultsCache> queryResultsCacheProvider; public TenantedAuthorizationFacetDefault( final List<ApplicationTenancyEvaluator> evaluators, diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationPostProcessor.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationPostProcessor.java index 9a8bdaa8ea..38747bf5e5 100644 --- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationPostProcessor.java +++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationPostProcessor.java @@ -66,6 +66,7 @@ extends ObjectSpecificationPostProcessorAbstract { @Inject UserService userService; @Inject @Lazy ApplicationUserRepository userRepository; @Inject Provider<QueryResultsCache> queryResultsCacheProvider; + @Autowired(required=false) List<ApplicationTenancyEvaluator> applicationTenancyEvaluators; @Inject diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java b/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java index c2613ac727..8d791d2131 100644 --- a/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java +++ b/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java @@ -31,6 +31,7 @@ import org.apache.isis.persistence.jpa.integration.changetracking.EntityChangeTr // @Service's EntityChangeTrackerDefault.class, + EntityChangeTrackerDefault.TransactionSubscriber.class, }) public class IsisModulePersistenceCommons { diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java index 620f5c96ec..f2b40b61f8 100644 --- a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java +++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java @@ -31,10 +31,12 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.apache.isis.applib.annotation.DomainObject; @@ -44,6 +46,7 @@ import org.apache.isis.applib.annotation.PriorityPrecedence; import org.apache.isis.applib.services.bookmark.Bookmark; import org.apache.isis.applib.services.iactn.Interaction; import org.apache.isis.applib.services.iactn.InteractionProvider; +import org.apache.isis.applib.services.iactnlayer.InteractionService; import org.apache.isis.applib.services.metrics.MetricsService; import org.apache.isis.applib.services.publishing.spi.EntityChanges; import org.apache.isis.applib.services.publishing.spi.EntityPropertyChange; @@ -85,6 +88,7 @@ import lombok.extern.log4j.Log4j2; * service <i>is</i> interaction-scoped, a new instance of the service is created for each interaction, and so the * data held in this service is private to each user's interaction. * </p> + * * @since 2.0 {@index} */ @Service @@ -102,6 +106,10 @@ implements HasEnlistedEntityChanges { + private final EntityPropertyChangePublisher entityPropertyChangePublisher; + private final EntityChangesPublisher entityChangesPublisher; + private final Provider<InteractionProvider> interactionProviderProvider; + /** * Contains a record for every objectId/propertyId that was changed. */ @@ -122,9 +130,17 @@ implements private final LongAdder entityChangeEventCount = new LongAdder(); private final AtomicBoolean persistentChangesEncountered = new AtomicBoolean(); - private final EntityPropertyChangePublisher entityPropertyChangePublisher; - private final EntityChangesPublisher entityChangesPublisher; - private final Provider<InteractionProvider> interactionProviderProvider; + + @Override + public void destroy() throws Exception { + enlistedPropertyChangeRecordsById.clear(); + entityPropertyChangeRecordsForPublishing.clear(); + changeKindByEnlistedAdapter.clear(); + + numberEntitiesLoaded.reset(); + entityChangeEventCount.reset(); + persistentChangesEncountered.set(false); + } Set<PropertyChangeRecord> snapshotPropertyChangeRecords() { // this code path has side-effects, it locks the result for this transaction, @@ -172,11 +188,58 @@ implements } /** - * TRANSACTION END BOUNDARY - * @apiNote intended to be called during before transaction completion by the framework internally + * Subscribes to transactions and forwards onto the current interaction's EntityChangeTracker, if available. + * + * <p> + * Note that this service has singleton-scope, unlike {@link EntityChangeTrackerDefault} which has + * {@link InteractionScope interaction scope}. The problem with using {@link EntityChangeTrackerDefault} as + * the direct subscriber is that if there's no {@link Interaction}, then Spring will fail to activate an instance resulting in an + * {@link org.springframework.beans.factory.support.ScopeNotActiveException}. Now, admittedly that exception + * gets swallowed in the call stack somewhere, but it's still not pretty. + * </p> + * + * <p> + * This design, instead, at least lets us check if there's an interaction in scope, and effectively ignore + * the call if not. + * </p> */ - @EventListener(value = TransactionBeforeCompletionEvent.class) @Order(PriorityPrecedence.LATE) - public void onTransactionCompleting(final TransactionBeforeCompletionEvent event) { + @Component + @Named("isis.persistence.commons.EntityChangeTrackerDefault.TransactionSubscriber") + @Priority(PriorityPrecedence.EARLY) + @Qualifier("default") + @RequiredArgsConstructor(onConstructor_ = {@Inject}) + public static class TransactionSubscriber { + + private final InteractionService interactionService; + private final Provider<EntityChangeTrackerDefault> entityChangeTrackerProvider; + + /** + * TRANSACTION END BOUNDARY + * @apiNote intended to be called during before transaction completion by the framework internally + */ + @EventListener(value = TransactionBeforeCompletionEvent.class) + @Order(PriorityPrecedence.LATE) + public void onTransactionCompleting(final TransactionBeforeCompletionEvent event) { + + if(!interactionService.isInInteraction()) { + // discard request is there is no interaction in scope. + // this shouldn't ever really occur, but some low-level (could be improved?) integration tests do + // hit this case. + return; + } + entityChangeTracker().onTransactionCompleting(event); + } + + private EntityChangeTrackerDefault entityChangeTracker() { + return entityChangeTrackerProvider.get(); + } + } + + /** + * As called by {@link TransactionSubscriber}, so long as there is an {@link Interaction} in + * {@link InteractionScope scope}. + */ + void onTransactionCompleting(final TransactionBeforeCompletionEvent event) { try { doPublish(); } finally { @@ -392,6 +455,4 @@ implements } - - } diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java index e127e956a1..d9ad45994c 100644 --- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java +++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java @@ -42,6 +42,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.transaction.interceptor.TransactionInterceptor; import org.apache.isis.applib.services.eventbus.EventBusService; +import org.apache.isis.applib.services.iactnlayer.InteractionService; import org.apache.isis.commons.internal.assertions._Assert; import org.apache.isis.commons.internal.base._NullSafe; import org.apache.isis.core.config.IsisConfiguration; @@ -157,7 +158,7 @@ public class IsisModulePersistenceJdoDatanucleus { final IsisConfiguration isisConfiguration, final DataSource dataSource, final MetaModelContext metaModelContext, - final EventBusService eventBusService, + final InteractionService interactionService, final ObjectLifecyclePublisher objectLifecyclePublisher, final Provider<EntityChangeTracker> entityChangeTrackerProvider, final IsisBeanTypeRegistry beanTypeRegistry, @@ -175,14 +176,14 @@ public class IsisModulePersistenceJdoDatanucleus { val pu = createDefaultPersistenceUnit(beanTypeRegistry); val pmf = new JDOPersistenceManagerFactory(pu, props); pmf.setConnectionFactory(dataSource); - integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, pmf); + integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService, pmf); return pmf; } @Override protected PersistenceManagerFactory newPersistenceManagerFactory(final String name) { val pmf = super.newPersistenceManagerFactory(name); pmf.setConnectionFactory(dataSource); //might be too late, anyway, not sure if this is ever called - integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, pmf); + integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService, pmf); return pmf; } }; @@ -334,12 +335,13 @@ public class IsisModulePersistenceJdoDatanucleus { final MetaModelContext metaModelContext, final Provider<EntityChangeTracker> entityChangeTrackerProvider, final ObjectLifecyclePublisher objectLifecyclePublisher, + final InteractionService interactionService, final PersistenceManagerFactory pmf) { // install JDO specific entity change listeners ... val jdoLifecycleListener = - new JdoLifecycleListener(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher); + new JdoLifecycleListener(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService); pmf.addInstanceLifecycleListener(jdoLifecycleListener, (Class[]) null); } diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java index 79f182388d..5eafecd564 100644 --- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java +++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java @@ -32,15 +32,13 @@ import javax.jdo.listener.StoreLifecycleListener; import org.datanucleus.enhancement.Persistable; -import org.apache.isis.applib.services.eventbus.EventBusService; +import org.apache.isis.applib.services.iactnlayer.InteractionService; import org.apache.isis.core.metamodel.context.MetaModelContext; import org.apache.isis.core.metamodel.facets.object.publish.entitychange.EntityChangePublishingFacet; import org.apache.isis.core.metamodel.objectmanager.ObjectManager.EntityAdaptingMode; import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher; import org.apache.isis.core.metamodel.spec.ManagedObject; import org.apache.isis.core.transaction.changetracking.EntityChangeTracker; -import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent; -import org.apache.isis.core.transaction.changetracking.events.PreStoreEvent; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -69,6 +67,7 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif private final @NonNull MetaModelContext metaModelContext; private final @NonNull Provider<EntityChangeTracker> entityChangeTrackerProvider; private final @NonNull ObjectLifecyclePublisher objectLifecyclePublisher; + private final @NonNull InteractionService interactionService; // -- CALLBACKS @@ -210,8 +209,8 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif // -- DEPENDENCIES - private EntityChangeTracker getEntityChangeTracker() { - return entityChangeTrackerProvider.get(); + private EntityChangeTracker entityChangeTracker() { + return interactionService.isInInteraction() ? entityChangeTrackerProvider.get() : EntityChangeTracker.NOOP; } } diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java index aea9fc23cc..1f409f9306 100644 --- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java +++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java @@ -60,7 +60,7 @@ import lombok.val; }) @TestPropertySource(IsisPresets.UseLog4j2Test) @Transactional @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - @DirtiesContext // doesn't seem to tidy up correctly ... I see InteractionService still injected into entities in the _next_ tests run (JpaExceptionTranslationTest_usingTransactional) +// @DirtiesContext // doesn't seem to tidy up correctly ... I see InteractionService still injected into entities in the _next_ tests run (JpaExceptionTranslationTest_usingTransactional) class JpaBootstrappingTest extends IsisIntegrationTestAbstract { @Inject private Optional<PlatformTransactionManager> platformTransactionManager; diff --git a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/TargetRespondListenerToResetQueryResultCache.java b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/TargetRespondListenerToResetQueryResultCache.java index 4cc1f1dd8a..3bb2cd8588 100644 --- a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/TargetRespondListenerToResetQueryResultCache.java +++ b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/TargetRespondListenerToResetQueryResultCache.java @@ -19,6 +19,7 @@ package org.apache.isis.viewer.wicket.viewer.wicketapp; import javax.inject.Inject; +import javax.inject.Provider; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -29,7 +30,7 @@ import lombok.extern.log4j.Log4j2; @Log4j2 class TargetRespondListenerToResetQueryResultCache implements AjaxRequestTarget.ITargetRespondListener { - @Inject private javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider; + @Inject private Provider<QueryResultsCache> queryResultsCacheProvider; @Override public void onTargetRespond(final AjaxRequestTarget target) {
