You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2023/12/11 10:37:41 UTC
(causeway) 23/25: Implement transaction-scoping and add associated changes
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3654-isis
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 9eb4bbf8bd8ef2a817bb3dddf6f8f7da45656760
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Sun Dec 10 13:36:00 2023 +0000
Implement transaction-scoping and add associated changes
Introduces transaction-scoping being governed by the new @TransactionScope annotation.
This makes way for the replacement of interaction-scoping of EntityChangeTrackerDefault with the newly introduced transaction-scoping.
The now redundant TransactionEventEmitter class is removed, signifying the complete migration to transaction-scoping.
---
.editorconfig | 2 +-
.../isis/applib/annotation/InteractionScope.java | 14 +++-
.../isis/applib/annotation/TransactionScope.java | 77 ++++++++++++++++++++++
.../interaction/IsisModuleCoreInteraction.java | 3 +
.../InteractionScopeBeanFactoryPostProcessor.java | 2 +-
... TransactionScopeBeanFactoryPostProcessor.java} | 12 ++--
.../isis/core/runtime/IsisModuleCoreRuntime.java | 2 -
.../runtime/events/TransactionEventEmitter.java | 66 -------------------
.../org/apache/isis/core/runtime/events/_Xray.java | 2 +-
.../publish/ObjectLifecyclePublisherDefault.java | 3 +
.../transaction/TransactionServiceSpring.java | 73 ++++++++++++--------
core/transaction/src/main/java/module-info.java | 1 +
.../events/TransactionCompletionStatus.java | 76 +++++++++++++++++++++
persistence/commons/pom.xml | 5 ++
persistence/commons/src/main/java/module-info.java | 1 +
.../changetracking/EntityChangeTrackerDefault.java | 19 +++---
16 files changed, 243 insertions(+), 115 deletions(-)
diff --git a/.editorconfig b/.editorconfig
index f7644a1dfb..a806c86944 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -112,7 +112,7 @@ ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = never
-ij_java_imports_layout = java.**,|,$java.**,|,javax.**,|,$javax.**,|,com.**,|,$com.**,|,org.**,|,$org.**,|,org.springframework.**,|,$org.springframework.**,|,org.apache.causeway.**,|,$org.apache.causeway.**,|,lombok.**,lombok.experimental.**,lombok.extern.log4j.**,lombok.extern.**,|,demoapp.**,|,*,|,$*
+ij_java_imports_layout = java.**,|,$java.**,|,javax.**,|,$javax.**,|,com.**,|,$com.**,|,org.**,|,$org.**,|,org.springframework.**,|,$org.springframework.**,|,org.apache.causeway.**,|,$org.apache.causeway.**,|,org.apache.isis.**,|,$org.apache.isis.**,|,lombok.**,lombok.experimental.**,lombok.extern.log4j.**,lombok.extern.**,|,demoapp.**,|,*,|,$*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
diff --git a/api/applib/src/main/java/org/apache/isis/applib/annotation/InteractionScope.java b/api/applib/src/main/java/org/apache/isis/applib/annotation/InteractionScope.java
index a8b2632d12..b8e1401f5b 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/annotation/InteractionScope.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/annotation/InteractionScope.java
@@ -25,6 +25,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.annotation.AliasFor;
/**
* {@code @InteractionScope} is a specialization of {@link Scope @Scope} for a
@@ -40,6 +42,7 @@ import org.springframework.context.annotation.Scope;
* @since 2.0 {@index}
* @see org.springframework.web.context.annotation.SessionScope
* @see org.springframework.web.context.annotation.ApplicationScope
+ * @see TransactionScope
* @see org.springframework.context.annotation.Scope
* @see org.springframework.stereotype.Component
* @see org.springframework.context.annotation.Bean
@@ -47,7 +50,16 @@ import org.springframework.context.annotation.Scope;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
-@Scope("interaction")
+@Scope(InteractionScope.SCOPE_NAME)
public @interface InteractionScope {
+ String SCOPE_NAME = "interaction";
+
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/annotation/TransactionScope.java b/api/applib/src/main/java/org/apache/isis/applib/annotation/TransactionScope.java
new file mode 100644
index 0000000000..a3177eca71
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/annotation/TransactionScope.java
@@ -0,0 +1,77 @@
+/*
+ * 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.isis.applib.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * {@code @TransactionScope} is a specialization of {@link Scope @Scope} for a
+ * service or component whose lifecycle is bound to the current top-level transaction,
+ * within an outer {@link InteractionScope interaction}.
+ *
+ * <p>Such services should additional implement Spring's
+ * {@link org.springframework.transaction.support.TransactionSynchronization} interface, defining the transaction
+ * lifecycle callbacks.
+ *
+ * <p>Specifically, {@code @TransactionScope} is a <em>composed annotation</em> that
+ * acts as a shortcut for {@code @Scope("transaction")}.
+ *
+ * <p>{@code @TransactionScope} may be used as a meta-annotation to create custom
+ * composed annotations.
+ *
+ * <p> Note that (apparently) the {@link org.springframework.transaction.support.TransactionSynchronization}
+ * infrastructure is only really intended to work with a single {@link org.springframework.transaction.PlatformTransactionManager}.
+ * And indeed, this is going to be typical case. However, our framework code does at least admit the possibility of
+ * multiple {@link org.springframework.transaction.PlatformTransactionManager}s being defined in the app. If that is
+ * the case, then (I believe) the callbacks of {@link org.springframework.transaction.support.TransactionSynchronization} might
+ * be called multiple times, once per {@link org.springframework.transaction.PlatformTransactionManager}. The framework
+ * currently doesn't provide any way to distinguish between these calls.
+ *
+ * @since 2.0 {@index}
+ * @see org.springframework.web.context.annotation.SessionScope
+ * @see org.springframework.web.context.annotation.ApplicationScope
+ * @see InteractionScope
+ * @see Scope
+ * @see org.springframework.stereotype.Component
+ * @see org.springframework.context.annotation.Bean
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Scope(TransactionScope.SCOPE_NAME)
+public @interface TransactionScope {
+
+ String SCOPE_NAME = "transaction";
+
+ /**
+ * Alias for {@link Scope#proxyMode}.
+ * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/core/interaction/src/main/java/org/apache/isis/core/interaction/IsisModuleCoreInteraction.java b/core/interaction/src/main/java/org/apache/isis/core/interaction/IsisModuleCoreInteraction.java
index 98748f3563..d8c269b1bd 100644
--- a/core/interaction/src/main/java/org/apache/isis/core/interaction/IsisModuleCoreInteraction.java
+++ b/core/interaction/src/main/java/org/apache/isis/core/interaction/IsisModuleCoreInteraction.java
@@ -18,6 +18,8 @@
*/
package org.apache.isis.core.interaction;
+import org.apache.isis.core.interaction.scope.TransactionScopeBeanFactoryPostProcessor;
+
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -27,6 +29,7 @@ import org.apache.isis.core.interaction.scope.InteractionScopeBeanFactoryPostPro
@Import({
InteractionScopeBeanFactoryPostProcessor.class,
+ TransactionScopeBeanFactoryPostProcessor.class,
})
public class IsisModuleCoreInteraction {
diff --git a/core/interaction/src/main/java/org/apache/isis/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java b/core/interaction/src/main/java/org/apache/isis/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java
index 8627359f42..57ef476adf 100644
--- a/core/interaction/src/main/java/org/apache/isis/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java
+++ b/core/interaction/src/main/java/org/apache/isis/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java
@@ -32,7 +32,7 @@ import lombok.val;
@Component
public class InteractionScopeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
- public static final String SCOPE_NAME = "interaction";
+ public static final String SCOPE_NAME = org.apache.isis.applib.annotation.InteractionScope.SCOPE_NAME;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
diff --git a/core/interaction/src/main/java/org/apache/isis/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java b/core/interaction/src/main/java/org/apache/isis/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java
similarity index 74%
copy from core/interaction/src/main/java/org/apache/isis/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java
copy to core/interaction/src/main/java/org/apache/isis/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java
index 8627359f42..d4e6b4b9af 100644
--- a/core/interaction/src/main/java/org/apache/isis/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java
+++ b/core/interaction/src/main/java/org/apache/isis/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
+import org.springframework.transaction.support.SimpleTransactionScope;
import lombok.val;
@@ -30,17 +31,14 @@ import lombok.val;
* @since 2.0
*/
@Component
-public class InteractionScopeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+public class TransactionScopeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
- public static final String SCOPE_NAME = "interaction";
+ public static final String SCOPE_NAME = org.apache.isis.applib.annotation.TransactionScope.SCOPE_NAME;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
- val interactionScope = new InteractionScope(beanFactory);
- beanFactory.registerScope(SCOPE_NAME, interactionScope);
+ val transactionScope = new SimpleTransactionScope();
+ beanFactory.registerScope(SCOPE_NAME, transactionScope);
}
- public static InteractionScopeLifecycleHandler lookupScope(ConfigurableBeanFactory beanFactory) throws BeansException {
- return (InteractionScopeLifecycleHandler) beanFactory.getRegisteredScope(SCOPE_NAME);
- }
}
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java
index b845cf1b72..e515605b86 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/IsisModuleCoreRuntime.java
@@ -24,7 +24,6 @@ import org.springframework.context.annotation.Import;
import org.apache.isis.core.interaction.IsisModuleCoreInteraction;
import org.apache.isis.core.metamodel.IsisModuleCoreMetamodel;
import org.apache.isis.core.runtime.events.MetamodelEventService;
-import org.apache.isis.core.runtime.events.TransactionEventEmitter;
import org.apache.isis.core.transaction.IsisModuleCoreTransaction;
import org.apache.isis.valuetypes.jodatime.integration.IsisModuleValJodatimeIntegration;
@@ -40,7 +39,6 @@ import org.apache.isis.valuetypes.jodatime.integration.IsisModuleValJodatimeInte
// @Service's
MetamodelEventService.class,
- TransactionEventEmitter.class,
// @Configuration's
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/events/TransactionEventEmitter.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/events/TransactionEventEmitter.java
deleted file mode 100644
index 2a50514f5b..0000000000
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/events/TransactionEventEmitter.java
+++ /dev/null
@@ -1,66 +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.isis.core.runtime.events;
-
-import javax.inject.Inject;
-
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-
-import org.apache.isis.applib.services.eventbus.EventBusService;
-import org.apache.isis.applib.services.iactnlayer.InteractionLayerTracker;
-import org.apache.isis.core.interaction.scope.TransactionBoundaryAware;
-import org.apache.isis.core.transaction.events.TransactionAfterCompletionEvent;
-import org.apache.isis.core.transaction.events.TransactionBeforeCompletionEvent;
-
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-
-@Service
-@RequiredArgsConstructor(onConstructor_ = {@Inject})
-public class TransactionEventEmitter
-implements TransactionSynchronization, TransactionBoundaryAware {
-
- private final EventBusService eventBusService;
- private final InteractionLayerTracker interactionLayerTracker;
-
- @Override
- public void beforeCompletion() {
- _Xray.txBeforeCompletion(interactionLayerTracker, "tx: beforeCompletion");
- eventBusService.post(TransactionBeforeCompletionEvent.instance());
- }
-
- @Override
- public void afterCompletion(final int status) {
- val event = TransactionAfterCompletionEvent.forStatus(status);
- eventBusService.post(event);
- _Xray.txAfterCompletion(interactionLayerTracker, String.format("tx: afterCompletion (%s)", event.name()));
- }
-
- @Override
- public void afterEnteringTransactionalBoundary(PlatformTransactionManager platformTransactionManager) {
- val isSynchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
- if(isSynchronizationActive) {
- TransactionSynchronizationManager.registerSynchronization(this);
- }
- }
-
-}
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/events/_Xray.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/events/_Xray.java
index 30a5801fb3..04833901de 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/events/_Xray.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/events/_Xray.java
@@ -29,7 +29,7 @@ import org.apache.isis.core.security.util.XrayUtil;
import lombok.val;
-final class _Xray {
+public final class _Xray {
static void addConfiguration(final ConfigurationViewService configurationService) {
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 abba0cad05..25bfb4919e 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
@@ -26,6 +26,8 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
+import org.apache.isis.applib.annotation.TransactionScope;
+
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
@@ -47,6 +49,7 @@ import lombok.RequiredArgsConstructor;
* @since 2.0 {@index}
*/
@Service
+@TransactionScope
@Named(IsisModuleCoreRuntimeServices.NAMESPACE + ".ObjectLifecyclePublisherDefault")
@Priority(PriorityPrecedence.EARLY)
@Qualifier("Default")
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
index 74a8c5ce63..5c1ee193ce 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/transaction/TransactionServiceSpring.java
@@ -33,7 +33,12 @@ import org.apache.isis.commons.internal.debug._Probe;
import org.apache.isis.core.interaction.scope.TransactionBoundaryAware;
import org.apache.isis.core.interaction.session.IsisInteraction;
+import org.apache.isis.core.runtime.events._Xray;
+import org.apache.isis.core.transaction.events.TransactionCompletionStatus;
+
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.event.EventListener;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
@@ -41,6 +46,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
@@ -82,25 +88,30 @@ implements
private final Can<PlatformTransactionManager> platformTransactionManagers;
private final InteractionLayerTracker interactionLayerTracker;
private final Can<PersistenceExceptionTranslator> persistenceExceptionTranslators;
- private final Can<TransactionBoundaryAware> transactionBoundaryAwareBeans;
+ private final ConfigurableListableBeanFactory configurableListableBeanFactory;
+// private final Can<TransactionBoundaryAware> transactionBoundaryAwareBeans;
@Inject
public TransactionServiceSpring(
final List<PlatformTransactionManager> platformTransactionManagers,
final List<PersistenceExceptionTranslator> persistenceExceptionTranslators,
final InteractionLayerTracker interactionLayerTracker,
- final List<TransactionBoundaryAware> transactionBoundaryAwareBeans) {
+ final ConfigurableListableBeanFactory configurableListableBeanFactory
+// , final List<TransactionBoundaryAware> transactionBoundaryAwareBeans
+ ) {
this.platformTransactionManagers = Can.ofCollection(platformTransactionManagers);
log.info("PlatformTransactionManagers: {}", platformTransactionManagers);
+ this.configurableListableBeanFactory = configurableListableBeanFactory;
+
this.persistenceExceptionTranslators = Can.ofCollection(persistenceExceptionTranslators);
log.info("PersistenceExceptionTranslators: {}", persistenceExceptionTranslators);
this.interactionLayerTracker = interactionLayerTracker;
- this.transactionBoundaryAwareBeans = Can.ofCollection(transactionBoundaryAwareBeans);
- log.info("TransactionBoundaryAwareBeans: {}", transactionBoundaryAwareBeans);
+// this.transactionBoundaryAwareBeans = Can.ofCollection(transactionBoundaryAwareBeans);
+// log.info("TransactionBoundaryAwareBeans: {}", transactionBoundaryAwareBeans);
}
// -- SPRING INTEGRATION
@@ -115,24 +126,30 @@ implements
try {
TransactionStatus tx = platformTransactionManager.getTransaction(def);
- if(tx.isNewTransaction()) {
- transactionBoundaryAwareBeans.forEach(tba -> tba.afterEnteringTransactionalBoundary(platformTransactionManager));
+// if(tx.isNewTransaction()) {
+// transactionBoundaryAwareBeans.forEach(tba -> tba.afterEnteringTransactionalBoundary(platformTransactionManager));
+// }
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ configurableListableBeanFactory.getBeansOfType(TransactionSynchronization.class)
+ .values()
+ .forEach(TransactionSynchronizationManager::registerSynchronization);
}
+
result = Try.call(callable)
.mapFailure(ex->translateExceptionIfPossible(ex, platformTransactionManager));
- if(tx.isNewTransaction()) {
- transactionBoundaryAwareBeans.forEach(tba -> tba.beforeLeavingTransactionalBoundary(platformTransactionManager));
- }
+// if(tx.isNewTransaction()) {
+// transactionBoundaryAwareBeans.forEach(tba -> tba.beforeLeavingTransactionalBoundary(platformTransactionManager));
+// }
if(result.isFailure()) {
platformTransactionManager.rollback(tx);
} else {
platformTransactionManager.commit(tx);
}
- if(tx.isNewTransaction()) {
- transactionBoundaryAwareBeans.forEach(tba -> tba.afterLeavingTransactionalBoundary(platformTransactionManager));
- }
+// if(tx.isNewTransaction()) {
+// transactionBoundaryAwareBeans.forEach(tba -> tba.afterLeavingTransactionalBoundary(platformTransactionManager));
+// }
} catch (Exception ex) {
return result!=null
@@ -254,17 +271,6 @@ implements
private ThreadLocal<LongAdder> txCounter = ThreadLocal.withInitial(LongAdder::new);
- /** TRANSACTION END BOUNDARY */
-
- /**
- * This is called by TransactionEventEmitter, which is a TransactionBoundaryAware bean.
- *
- * @param event
- */
- @EventListener(TransactionAfterCompletionEvent.class)
- public void onTransactionEnded(final TransactionAfterCompletionEvent event) {
- txCounter.get().increment();
- }
// -- HELPER
@@ -356,12 +362,19 @@ implements
// either participate in existing or create new transaction
TransactionStatus txStatus = txManager.getTransaction(def);
- if(txStatus==null // in support of JUnit testing (TransactionManagers might be mocked or hollow stubs)
- || !txStatus.isNewTransaction()) {
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ configurableListableBeanFactory.getBeansOfType(TransactionSynchronization.class)
+ .values()
+ .forEach(TransactionSynchronizationManager::registerSynchronization);
+ }
+
+ if(
+// txStatus==null || // in support of JUnit testing (TransactionManagers might be mocked or hollow stubs)
+ !txStatus.isNewTransaction()) {
// we are participating in an exiting transaction (or testing), nothing to do
return;
}
- transactionBoundaryAwareBeans.forEach(tbab -> tbab.afterEnteringTransactionalBoundary(txManager));
+// transactionBoundaryAwareBeans.forEach(tbab -> tbab.afterEnteringTransactionalBoundary(txManager));
// we have created a new transaction, so need to provide a CloseTask
onCloseTasks.add(
@@ -369,13 +382,17 @@ implements
txStatus,
txManager.getClass().getName(), // info to be used for display in case of errors
() -> {
- transactionBoundaryAwareBeans.forEach(tbab -> tbab.beforeLeavingTransactionalBoundary(txManager));
+// transactionBoundaryAwareBeans.forEach(tbab -> tbab.beforeLeavingTransactionalBoundary(txManager));
+ _Xray.txBeforeCompletion(interactionLayerTracker, "tx: beforeCompletion");
if (txStatus.isRollbackOnly()) {
txManager.rollback(txStatus);
} else {
txManager.commit(txStatus);
+ val event = TransactionCompletionStatus.COMMITTED;
+ _Xray.txAfterCompletion(interactionLayerTracker, String.format("tx: afterCompletion (%s)", event.name()));
}
- transactionBoundaryAwareBeans.forEach(tbab -> tbab.afterLeavingTransactionalBoundary(txManager));
+// transactionBoundaryAwareBeans.forEach(tbab -> tbab.afterLeavingTransactionalBoundary(txManager));
+ txCounter.get().increment();
}
)
);
diff --git a/core/transaction/src/main/java/module-info.java b/core/transaction/src/main/java/module-info.java
index fa1c303f22..e205ff8528 100644
--- a/core/transaction/src/main/java/module-info.java
+++ b/core/transaction/src/main/java/module-info.java
@@ -34,4 +34,5 @@ module org.apache.isis.core.transaction {
requires spring.context;
requires spring.core;
requires spring.tx;
+ requires java.transaction;
}
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/events/TransactionCompletionStatus.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/events/TransactionCompletionStatus.java
new file mode 100644
index 0000000000..1ac9ecb05a
--- /dev/null
+++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/events/TransactionCompletionStatus.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.isis.core.transaction.events;
+
+import javax.transaction.Status;
+
+/**
+ * @since 2.0 {@index}
+ * @see Status
+ */
+public enum TransactionCompletionStatus {
+
+ /** Completion status in case of proper commit. */
+ COMMITTED,
+
+ /** Completion status in case of proper rollback. */
+ ROLLED_BACK,
+
+ /** Completion status in case of heuristic mixed completion or system errors. */
+ UNKNOWN,
+ ;
+
+ /**
+ * @param status field from {@link Status}.
+ * @return
+ */
+ public static TransactionCompletionStatus forStatus(int status) {
+ switch (status) {
+ case 3:
+ // int STATUS_COMMITTED = 3;
+ return COMMITTED;
+ case 4:
+ // int STATUS_ROLLEDBACK = 4;
+ return ROLLED_BACK;
+ default:
+ // int STATUS_ACTIVE = 0;
+ // int STATUS_MARKED_ROLLBACK = 1;
+ // int STATUS_PREPARED = 2;
+ // int STATUS_UNKNOWN = 5;
+ // int STATUS_NO_TRANSACTION = 6;
+ // int STATUS_PREPARING = 7;
+ // int STATUS_COMMITTING = 8;
+ // int STATUS_ROLLING_BACK = 9;
+ return UNKNOWN;
+ }
+ }
+
+ public boolean isCommitted() {
+ return this == COMMITTED;
+ }
+
+ public boolean isRolledBack() {
+ return this == ROLLED_BACK;
+ }
+
+ public boolean isUnknown() {
+ return this == UNKNOWN;
+ }
+
+}
diff --git a/persistence/commons/pom.xml b/persistence/commons/pom.xml
index 5b8d732e6e..a5adfbe56c 100644
--- a/persistence/commons/pom.xml
+++ b/persistence/commons/pom.xml
@@ -58,6 +58,11 @@
<artifactId>isis-core-runtime</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-tx</artifactId>
+ </dependency>
+
<!-- TESTING -->
<dependency>
diff --git a/persistence/commons/src/main/java/module-info.java b/persistence/commons/src/main/java/module-info.java
index 90363f6adf..f9ee74fa63 100644
--- a/persistence/commons/src/main/java/module-info.java
+++ b/persistence/commons/src/main/java/module-info.java
@@ -39,4 +39,5 @@ module org.apache.isis.persistence.commons {
requires spring.beans;
requires spring.context;
requires spring.core;
+ requires spring.tx;
}
diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/isis/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
index 532a31b04a..d0704d673d 100644
--- a/persistence/commons/src/main/java/org/apache/isis/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
@@ -32,12 +32,17 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
+import org.apache.isis.applib.annotation.TransactionScope;
+
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
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.springframework.transaction.support.TransactionSynchronization;
import org.apache.isis.applib.annotation.DomainObject;
import org.apache.isis.applib.annotation.EntityChangeKind;
@@ -78,24 +83,22 @@ import lombok.val;
import lombok.extern.log4j.Log4j2;
/**
- * This service keeps track of all of the changes within a transactoin, for entities for which entity property change
+ * This object keeps track of all of the changes within a transaction, for entities for which entity property change
* publishing is enabled (typically using the
* {@link DomainObject#entityChangePublishing() @DomainObject(entityChangePublishing=)} annotation attribute.
*
* <p>
- * The service is {@link InteractionScope}d. In theory this could happen multiple times per interaction, so the
- * data structures are cleared on each commit for potential reuse within the same interaction. (Of course, because the
- * 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.
+ * The service is transaction-scoped and implements Spring's TransactionSynchronization interface, meaning that
+ * Spring is responsible for calling it thereafter.
* </p>
*
* @since 2.0 {@index}
*/
@Service
+@TransactionScope
@Named("isis.persistence.commons.EntityChangeTrackerDefault")
@Priority(PriorityPrecedence.EARLY)
@Qualifier("default")
-@InteractionScope // see note above regarding this
@RequiredArgsConstructor(onConstructor_ = {@Inject})
@Log4j2
public class EntityChangeTrackerDefault
@@ -103,8 +106,8 @@ implements
MetricsService,
EntityChangeTracker,
HasEnlistedEntityPropertyChanges,
- HasEnlistedEntityChanges {
-
+ HasEnlistedEntityChanges,
+ TransactionSynchronization {
private final EntityPropertyChangePublisher entityPropertyChangePublisher;
private final EntityChangesPublisher entityChangesPublisher;