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/13 12:24:08 UTC
(causeway) branch CAUSEWAY-3654 updated (a376a3535a -> b0d31d1305)
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a change to branch CAUSEWAY-3654
in repository https://gitbox.apache.org/repos/asf/causeway.git
discard a376a3535a CAUSEWAY-3654: refactors ScopedObjectsHolder to prevent stackoverflow...
discard 15902ba61b CAUSEWAY-3654: adds guards to handle afterCompletion call
discard d627fe17af CAUSEWAY-3654: removes accidentally added file
discard 8a23c115a9 CAUSEWAY-3654: minor cleanup is all
discard 7c0fb3c1fe CAUSEWAY-3654: fixes compile issues in tests
discard 2755d74bf7 CAUSEWAY-3564: introduces TransactionScope annotation and ...
add 30c94027a7 CAUSEWAY-2297: remove unused imports
add 8aa41cc27a CAUSEWAY-3657: relax HiddenTypeFacet visibility for interfaces
new bcb3f94e18 CAUSEWAY-3564: introduces TransactionScope annotation and ...
new 3fb0511ef4 CAUSEWAY-3654: fixes compile issues in tests
new 07cb0a1e0d CAUSEWAY-3654: minor cleanup is all
new 252ada938e CAUSEWAY-3654: removes accidentally added file
new d7551153aa CAUSEWAY-3654: adds guards to handle afterCompletion call
new b0d31d1305 CAUSEWAY-3654: refactors ScopedObjectsHolder to prevent stackoverflow...
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (a376a3535a)
\
N -- N -- N refs/heads/CAUSEWAY-3654 (b0d31d1305)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../object/hidden/HiddenTypeFacetFromAuthorization.java | 12 ++++++++++++
.../layouts/integtest/Layout_Counter_IntegTest.java | 1 -
.../layouts/integtest/Layout_SimpleObject_IntegTest.java | 2 --
.../restfulobjects/viewer/resources/VersionReprRenderer.java | 5 +----
4 files changed, 13 insertions(+), 7 deletions(-)
(causeway) 01/06: CAUSEWAY-3564: introduces TransactionScope annotation and ...
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3654
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit bcb3f94e1834b653327d907abc27df4471f33786
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Dec 11 15:49:39 2023 +0000
CAUSEWAY-3564: introduces TransactionScope annotation and ...
... reworks InteractionServiceDefault and TransactionServiceSpring to use more of Spring's inbuilt xactn support
Specifically, using TransactionSynchronizationManager and TransactionSynchronization to allow suspend/resume
(meaning REQUIRES_NEW is supported).
In particular, the InteractionAwareTransactionBoundaryHandler service is removed, its functionality moved into
TransactionServiceSpring, and also removed TransactionBoundaryAware interface and callbacks - instead,
TransactionSynchronization can be used.
---
.editorconfig | 2 +-
...ntentNegotiationServiceOrgApacheCausewayV1.adoc | 19 ++
...tionServiceOrgApacheCausewayV1_usage-notes.adoc | 6 +
antora/supplemental-ui/causeway-favicon.png | Bin 0 -> 2143 bytes
.../applib/annotation/InteractionScope.java | 14 +-
.../applib/annotation/TransactionScope.java | 79 +++++
.../services/xactn/TransactionalProcessor.java | 17 +-
core/interaction/src/main/java/module-info.java | 1 -
.../interaction/CausewayModuleCoreInteraction.java | 4 +-
...teractionAwareTransactionalBoundaryHandler.java | 175 ----------
.../InteractionScopeBeanFactoryPostProcessor.java | 2 +-
.../interaction/scope/StackedTransactionScope.java | 184 ++++++++++
.../TransactionScopeBeanFactoryPostProcessor.java} | 24 +-
.../metamodel/objectmanager/ObjectCreator.java | 4 +-
.../objectlifecycle/ObjectLifecyclePublisher.java | 4 +
.../specloader/specimpl/IntrospectionState.java} | 48 +--
.../core/runtime/CausewayModuleCoreRuntime.java | 2 -
.../runtime/events/TransactionEventEmitter.java | 67 ----
.../apache/causeway/core/runtime/events/_Xray.java | 2 +-
.../runtimeservices/src/main/java/module-info.java | 3 +-
.../EntityPropertyChangePublisherDefault.java | 4 +-
.../publish/ObjectLifecyclePublisherDefault.java | 2 +
.../session/InteractionServiceDefault.java | 38 +--
.../transaction/TransactionServiceSpring.java | 218 ++++++++++--
core/transaction/src/main/java/module-info.java | 3 +-
...Event.java => TransactionCompletionStatus.java} | 31 +-
.../integtests/OutboxRestClient_E2eTest.java | 6 +-
.../spiimpl/SessionSubscriberForSessionLog.java | 19 +-
persistence/commons/src/main/java/module-info.java | 3 +-
.../commons/CausewayModulePersistenceCommons.java | 1 -
.../changetracking/EntityChangeTrackerDefault.java | 118 ++-----
.../CausewayModulePersistenceJdoDatanucleus.java | 1 +
.../metamodel/facets/entity/JdoEntityFacet.java | 1 +
...actionRollbackTest_usingInteractionService.java | 2 -
...actionRollbackTest_usingTransactionService.java | 2 -
.../testdomain/conf/Configuration_headless.java | 1 -
.../publishing/PublishingTestFactoryAbstract.java | 2 -
.../util/interaction/InteractionBoundaryProbe.java | 3 -
.../rename-all-published-sources-to-causeway.jsh | 380 +++++++++++++++++++++
.../branding/BrandingUiServiceDefault.java | 2 -
...r.restfulobjects.applib.client.UriBuilderPlugin | 1 -
...ntentNegotiationServiceOrgApacheCausewayV1.java | 108 ++++++
.../viewer/src/main/java/module-info.java | 3 +-
43 files changed, 1137 insertions(+), 469 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/antora/components/refguide-index/modules/viewer/pages/index/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.adoc b/antora/components/refguide-index/modules/viewer/pages/index/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.adoc
new file mode 100644
index 0000000000..6965204c15
--- /dev/null
+++ b/antora/components/refguide-index/modules/viewer/pages/index/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.adoc
@@ -0,0 +1,19 @@
+= ContentNegotiationServiceOrgApacheIsisV1
+:Notice: 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 ag [...]
+
+== API
+
+[source,java]
+.ContentNegotiationServiceOrgApacheIsisV1.java
+----
+class ContentNegotiationServiceOrgApacheIsisV1 {
+ public static final String ACCEPT_PROFILE;
+ Response.ResponseBuilder buildResponse(IResourceContext resourceContext, ManagedObject objectAdapter)
+ Response.ResponseBuilder buildResponse(IResourceContext resourceContext, ManagedProperty objectAndProperty)
+ Response.ResponseBuilder buildResponse(IResourceContext resourceContext, ManagedCollection managedCollection)
+ Response.ResponseBuilder buildResponse(IResourceContext resourceContext, ManagedAction objectAndAction)
+ Response.ResponseBuilder buildResponse(IResourceContext resourceContext, ObjectAndActionInvocation objectAndActionInvocation)
+}
+----
+
+include::hooks/ContentNegotiationServiceOrgApacheIsisV1_usage-notes.adoc[]
diff --git a/antora/components/refguide-index/modules/viewer/pages/index/restfulobjects/rendering/service/conneg/hooks/ContentNegotiationServiceOrgApacheCausewayV1_usage-notes.adoc b/antora/components/refguide-index/modules/viewer/pages/index/restfulobjects/rendering/service/conneg/hooks/ContentNegotiationServiceOrgApacheCausewayV1_usage-notes.adoc
new file mode 100644
index 0000000000..5dd9b62ebc
--- /dev/null
+++ b/antora/components/refguide-index/modules/viewer/pages/index/restfulobjects/rendering/service/conneg/hooks/ContentNegotiationServiceOrgApacheCausewayV1_usage-notes.adoc
@@ -0,0 +1,6 @@
+:Notice: 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 ag [...]
+
+
+This service returns xref:vro:ROOT:content-negotiation/apache-causeway-v2-profile.adoc[simplified representations]
+
+
diff --git a/antora/supplemental-ui/causeway-favicon.png b/antora/supplemental-ui/causeway-favicon.png
new file mode 100644
index 0000000000..258658996e
Binary files /dev/null and b/antora/supplemental-ui/causeway-favicon.png differ
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/annotation/InteractionScope.java b/api/applib/src/main/java/org/apache/causeway/applib/annotation/InteractionScope.java
index 875f16a2ae..894b347fa0 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/annotation/InteractionScope.java
+++ b/api/applib/src/main/java/org/apache/causeway/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/causeway/applib/annotation/TransactionScope.java b/api/applib/src/main/java/org/apache/causeway/applib/annotation/TransactionScope.java
new file mode 100644
index 0000000000..681a2c17c0
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/causeway/applib/annotation/TransactionScope.java
@@ -0,0 +1,79 @@
+/*
+ * 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.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";
+
+ /**
+ * Proxying <i>must</i> be enabled, because we inject {@link TransactionScope}d beans
+ * into beans with wider scopes.
+ *
+ * <p>Alias for {@link Scope#proxyMode}.
+ */
+ @AliasFor(annotation = Scope.class)
+ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
+
+}
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/services/xactn/TransactionalProcessor.java b/api/applib/src/main/java/org/apache/causeway/applib/services/xactn/TransactionalProcessor.java
index ebd70f2d79..868f0252f7 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/services/xactn/TransactionalProcessor.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/services/xactn/TransactionalProcessor.java
@@ -44,6 +44,10 @@ public interface TransactionalProcessor {
/**
* Runs given {@code callable} with a transactional boundary, where the detailed transactional behavior
* is governed by given {@link TransactionDefinition} {@code def}.
+ *
+ * @param def - transaction definition, in particular whether to use existing or start new transaction. Requires only a single {@link org.springframework.transaction.PlatformTransactionManager} to be configured (unless a {@link org.springframework.transaction.support.TransactionTemplate} is provided which wraps a specific {@link org.springframework.transaction.PlatformTransactionManager}.
+ * @param callable - the work to be performed within the transaction.
+ * @return {@link Try} of calling given {@code callable}
* @return {@link Try} of calling given {@code callable}
*/
<T> Try<T> callTransactional(TransactionDefinition def, Callable<T> callable);
@@ -51,6 +55,10 @@ public interface TransactionalProcessor {
/**
* Runs given {@code runnable} with a transactional boundary, where the detailed transactional behavior
* is governed by given {@link TransactionDefinition} {@code def}.
+ *
+ * @param def - transaction definition, in particular whether to use existing or start new transaction. Requires only a single {@link org.springframework.transaction.PlatformTransactionManager} to be configured (unless a {@link org.springframework.transaction.support.TransactionTemplate} is provided which wraps a specific {@link org.springframework.transaction.PlatformTransactionManager}.
+ * @param runnable - the work to be performed within the transaction.
+ * @return {@link Try} of calling given {@code callable}
*/
default Try<Void> runTransactional(final TransactionDefinition def, final ThrowingRunnable runnable) {
return callTransactional(def, ThrowingRunnable.toCallable(runnable));
@@ -61,8 +69,9 @@ public interface TransactionalProcessor {
/**
* Runs given {@code callable} with a transactional boundary, where the detailed transactional behavior
* is governed by given {@link Propagation} {@code propagation}.
- * <p>
- * More fine grained control is given via {@link #callTransactional(TransactionDefinition, Callable)}
+ *
+ * @param propagation - transaction propagation, ie whether to use existing or start new transaction. Requires only a single {@link org.springframework.transaction.PlatformTransactionManager} to be configured. For more control, use {@link #callTransactional(TransactionDefinition, Callable)} and pass in a {@link org.springframework.transaction.support.TransactionTemplate} is provided which wraps a specific {@link org.springframework.transaction.PlatformTransactionManager}.
+ * @param callable - the work to be performed within the transaction.
* @return {@link Try} of calling given {@code callable}
*/
default <T> Try<T> callTransactional(final Propagation propagation, final Callable<T> callable) {
@@ -77,6 +86,10 @@ public interface TransactionalProcessor {
* <p>
* More fine grained control is given via
* {@link #runTransactional(TransactionDefinition, ThrowingRunnable)}
+ *
+ * @param propagation - transaction propagation, ie whether to use existing or start new transaction. Requires only a single {@link org.springframework.transaction.PlatformTransactionManager} to be configured. For more control, use {@link #callTransactional(TransactionDefinition, Callable)} and pass in a {@link org.springframework.transaction.support.TransactionTemplate} is provided which wraps a specific {@link org.springframework.transaction.PlatformTransactionManager}.
+ * @param runnable - the work to be performed within the transaction.
+ * @return {@link Try} of calling given {@code callable}
*/
default Try<Void> runTransactional(final Propagation propagation, final ThrowingRunnable runnable) {
return callTransactional(propagation, ThrowingRunnable.toCallable(runnable));
diff --git a/core/interaction/src/main/java/module-info.java b/core/interaction/src/main/java/module-info.java
index 21e6719613..c13a521cc3 100644
--- a/core/interaction/src/main/java/module-info.java
+++ b/core/interaction/src/main/java/module-info.java
@@ -18,7 +18,6 @@
*/
module org.apache.causeway.core.interaction {
exports org.apache.causeway.core.interaction;
- exports org.apache.causeway.core.interaction.integration;
exports org.apache.causeway.core.interaction.scope;
exports org.apache.causeway.core.interaction.session;
diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/CausewayModuleCoreInteraction.java b/core/interaction/src/main/java/org/apache/causeway/core/interaction/CausewayModuleCoreInteraction.java
index c915622590..13ff6e5897 100644
--- a/core/interaction/src/main/java/org/apache/causeway/core/interaction/CausewayModuleCoreInteraction.java
+++ b/core/interaction/src/main/java/org/apache/causeway/core/interaction/CausewayModuleCoreInteraction.java
@@ -21,14 +21,14 @@ package org.apache.causeway.core.interaction;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-import org.apache.causeway.core.interaction.integration.InteractionAwareTransactionalBoundaryHandler;
+import org.apache.causeway.core.interaction.scope.TransactionScopeBeanFactoryPostProcessor;
import org.apache.causeway.core.interaction.scope.InteractionScopeBeanFactoryPostProcessor;
@Configuration
@Import({
InteractionScopeBeanFactoryPostProcessor.class,
- InteractionAwareTransactionalBoundaryHandler.class
+ TransactionScopeBeanFactoryPostProcessor.class,
})
public class CausewayModuleCoreInteraction {
diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/integration/InteractionAwareTransactionalBoundaryHandler.java b/core/interaction/src/main/java/org/apache/causeway/core/interaction/integration/InteractionAwareTransactionalBoundaryHandler.java
deleted file mode 100644
index d5164556c8..0000000000
--- a/core/interaction/src/main/java/org/apache/causeway/core/interaction/integration/InteractionAwareTransactionalBoundaryHandler.java
+++ /dev/null
@@ -1,175 +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.core.interaction.integration;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Consumer;
-
-import javax.annotation.Priority;
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import org.apache.causeway.core.interaction.CausewayModuleCoreInteraction;
-import org.springframework.beans.factory.annotation.Qualifier;
-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.TransactionTemplate;
-
-import org.apache.causeway.applib.annotation.PriorityPrecedence;
-import org.apache.causeway.commons.collections.Can;
-import org.apache.causeway.commons.functional.ThrowingRunnable;
-import org.apache.causeway.commons.internal.collections._Lists;
-import org.apache.causeway.commons.internal.debug._Probe;
-import org.apache.causeway.core.interaction.session.CausewayInteraction;
-
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.Value;
-import lombok.val;
-import lombok.extern.log4j.Log4j2;
-
-@Service
-@Named(InteractionAwareTransactionalBoundaryHandler.LOGICAL_TYPE_NAME)
-@Priority(PriorityPrecedence.MIDPOINT)
-@Qualifier("Default")
-@Log4j2
-public class InteractionAwareTransactionalBoundaryHandler {
-
- public static final String LOGICAL_TYPE_NAME = CausewayModuleCoreInteraction.NAMESPACE + ".InteractionAwareTransactionalBoundaryHandler";
-
- private final Can<PlatformTransactionManager> txManagers;
-
- @Inject
- public InteractionAwareTransactionalBoundaryHandler(final List<PlatformTransactionManager> txManagers) {
- this.txManagers = Can.ofCollection(txManagers);
- }
-
- // -- OPEN
-
- public void onOpen(final @NonNull CausewayInteraction interaction) {
-
- if (log.isDebugEnabled()) {
- log.debug("opening on {}", _Probe.currentThreadId());
- }
-
- if(txManagers.isEmpty()) {
- return; // nothing to do
- }
-
- val onCloseTasks = _Lists.<CloseTask>newArrayList(txManagers.size());
- interaction.putAttribute(OnCloseHandle.class, new OnCloseHandle(onCloseTasks));
-
- txManagers.forEach(txManager->newTransactionOrParticipateInExisting(txManager, onCloseTasks::add));
-
- }
-
- // -- CLOSE
-
- public void onClose(final @NonNull CausewayInteraction interaction) {
-
- if (log.isDebugEnabled()) {
- log.debug("closing on {}", _Probe.currentThreadId());
- }
-
- if(txManagers.isEmpty()) {
- return; // nothing to do
- }
-
- Optional.ofNullable(interaction.getAttribute(OnCloseHandle.class))
- .ifPresent(OnCloseHandle::runOnCloseTasks);
-
- }
-
- public void requestRollback(final @NonNull CausewayInteraction interaction) {
- Optional.ofNullable(interaction.getAttribute(OnCloseHandle.class))
- .ifPresent(OnCloseHandle::requestRollback);
- }
-
- // -- HELPER
-
- private void newTransactionOrParticipateInExisting(
- final PlatformTransactionManager txManager,
- final Consumer<CloseTask> onNewCloseTask) {
-
- val txTemplate = new TransactionTemplate(txManager);
- txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
-
- // either participate in existing or create new transaction
- val txStatus = txManager.getTransaction(txTemplate);
- 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;
- }
-
- // we have created a new transaction, so need to provide a CloseTask
-
- onNewCloseTask.accept(
- new CloseTask(
- txStatus,
- txManager.getClass().getName(), // info to be used for display in case of errors
- ()->{
-
- if(txStatus.isRollbackOnly()) {
- txManager.rollback(txStatus);
- } else {
- txManager.commit(txStatus);
- }
-
- }));
- }
-
- @Value
- private static class CloseTask {
- private final @NonNull TransactionStatus txStatus;
- private final @NonNull String onErrorInfo;
- private final @NonNull ThrowingRunnable runnable;
- }
-
- @RequiredArgsConstructor
- private static class OnCloseHandle {
- private final @NonNull List<CloseTask> onCloseTasks;
- void requestRollback() {
- onCloseTasks.forEach(onCloseTask->{
- onCloseTask.txStatus.setRollbackOnly();
- });
- }
- void runOnCloseTasks() {
- onCloseTasks.forEach(onCloseTask->{
-
- try {
- onCloseTask.getRunnable().run();
- } catch(final Throwable ex) {
- // ignore
- log.error(
- "failed to close transactional boundary using transaction-manager {}; "
- + "continuing to avoid memory leakage",
- onCloseTask.getOnErrorInfo(),
- ex);
- }
-
- });
- }
- }
-
-
-}
diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java b/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java
index 02c0a4d07e..1b9ea5b7b6 100644
--- a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/InteractionScopeBeanFactoryPostProcessor.java
+++ b/core/interaction/src/main/java/org/apache/causeway/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.causeway.applib.annotation.InteractionScope.SCOPE_NAME;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/StackedTransactionScope.java b/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/StackedTransactionScope.java
new file mode 100644
index 0000000000..80ca608509
--- /dev/null
+++ b/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/StackedTransactionScope.java
@@ -0,0 +1,184 @@
+
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.interaction.scope;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Stack;
+
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.config.Scope;
+import org.springframework.lang.Nullable;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+public class StackedTransactionScope implements Scope {
+
+
+ @Override
+ public Object get(String name, ObjectFactory<?> objectFactory) {
+
+ Object key = currentKeyOnTransactionStack();
+
+ ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(key);
+ if (scopedObjects == null) {
+ scopedObjects = new ScopedObjectsHolder();
+ TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
+ TransactionSynchronizationManager.bindResource(key, scopedObjects);
+ }
+ // NOTE: Do NOT modify the following to use Map::computeIfAbsent. For details,
+ // see https://github.com/spring-projects/spring-framework/issues/25801.
+ Object scopedObject = scopedObjects.scopedInstances.get(name);
+ if (scopedObject == null) {
+ scopedObject = objectFactory.getObject();
+ scopedObjects.scopedInstances.put(name, scopedObject);
+ }
+ return scopedObject;
+ }
+
+ @Override
+ @Nullable
+ public Object remove(String name) {
+ Object key = currentKeyOnTransactionStack();
+ ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(key);
+ if (scopedObjects != null) {
+ scopedObjects.destructionCallbacks.remove(name);
+ return scopedObjects.scopedInstances.remove(name);
+ }
+ else {
+ return null;
+ }
+ }
+
+ @Override
+ public void registerDestructionCallback(String name, Runnable callback) {
+ ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(this);
+ if (scopedObjects != null) {
+ scopedObjects.destructionCallbacks.put(name, callback);
+ }
+ }
+
+ private static final ThreadLocal<Stack<Object>> transactionStackThreadLocal = ThreadLocal.withInitial(() -> {
+ Stack<Object> stack = new Stack<>();
+ stack.push(new Object());
+ return stack;
+ });
+
+ /**
+ * Maintains a stack of keys, where the top-most is the key managed by {@link TransactionSynchronizationManager}
+ * holding the {@link ScopedObjectsHolder} for the current transaction.
+ *
+ * <p>
+ * If a transaction is suspended, then the {@link CleanupSynchronization#suspend() suspend} callback is used
+ * to pop a new key onto the stack, unbinding the previous key's resources (in other words, the
+ * {@link org.apache.causeway.applib.annotation.TransactionScope transaction-scope}d beans of the suspended
+ * transaction) from {@link TransactionSynchronizationManager}. As transaction-scoped beans are then resolved,
+ * they will be associated with the new key.
+ * </p>
+ *
+ * <p>
+ * Conversely, when a tranaction is resumed, then the process is reversed; the old key is popped, and the previous
+ * key is rebound to the {@link TransactionSynchronizationManager}, meaning that the previous transaction's
+ * {@link org.apache.causeway.applib.annotation.TransactionScope transaction-scope}d beans are brought back.
+ * </p>
+ *
+ * @see #currentKeyOnTransactionStack()
+ * @see #pushNewKeyOntoTransactionStack()
+ * @see #popOldKeyFromTransactionStack()
+ * @see #transactionStackThreadLocal
+ */
+ private static Stack<Object> transactionStack() {
+ return transactionStackThreadLocal.get();
+ }
+ /**
+ * @see #transactionStack()
+ */
+ private Object currentKeyOnTransactionStack() {
+ return transactionStack().peek();
+ }
+ /**
+ * @see #transactionStack()
+ */
+ private static void pushNewKeyOntoTransactionStack() {
+ transactionStack().push(new Object());
+ }
+ /**
+ * @see #transactionStack()
+ */
+ private static void popOldKeyFromTransactionStack() {
+ transactionStack().pop();
+ }
+
+
+ @Override
+ @Nullable
+ public Object resolveContextualObject(String key) {
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public String getConversationId() {
+ return TransactionSynchronizationManager.getCurrentTransactionName();
+ }
+
+
+ /**
+ * Holder for scoped objects.
+ */
+ static class ScopedObjectsHolder {
+
+ final Map<String, Object> scopedInstances = new HashMap<>();
+
+ final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<>();
+ }
+
+
+ private class CleanupSynchronization implements TransactionSynchronization {
+
+ private final ScopedObjectsHolder scopedObjects;
+
+ public CleanupSynchronization(ScopedObjectsHolder scopedObjects) {
+ this.scopedObjects = scopedObjects;
+ }
+
+ @Override
+ public void suspend() {
+ TransactionSynchronizationManager.unbindResource(currentKeyOnTransactionStack());
+ pushNewKeyOntoTransactionStack(); // subsequent calls to obtain a @TransactionScope'd bean will be against this key
+ }
+
+ @Override
+ public void resume() {
+ popOldKeyFromTransactionStack(); // the now-completed transaction's @TransactionScope'd beans are no longer required, and will be GC'd.
+ TransactionSynchronizationManager.bindResource(currentKeyOnTransactionStack(), this.scopedObjects);
+ }
+
+ @Override
+ public void afterCompletion(int status) {
+ TransactionSynchronizationManager.unbindResourceIfPossible(StackedTransactionScope.this.currentKeyOnTransactionStack());
+ for (Runnable callback : this.scopedObjects.destructionCallbacks.values()) {
+ callback.run();
+ }
+ this.scopedObjects.destructionCallbacks.clear();
+ this.scopedObjects.scopedInstances.clear();
+ }
+ }
+
+}
diff --git a/core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionBeforeCompletionEvent.java b/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java
similarity index 50%
rename from core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionBeforeCompletionEvent.java
rename to core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java
index 8bad9c8f54..51deb40f56 100644
--- a/core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionBeforeCompletionEvent.java
+++ b/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java
@@ -16,17 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.causeway.core.transaction.events;
+package org.apache.causeway.core.interaction.scope;
-import lombok.Value;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.stereotype.Component;
-@Value
-public class TransactionBeforeCompletionEvent {
+import lombok.val;
- private final static TransactionBeforeCompletionEvent INSTANCE = new TransactionBeforeCompletionEvent();
+/**
+ * @since 2.0
+ */
+@Component
+public class TransactionScopeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+
+ public static final String SCOPE_NAME = org.apache.causeway.applib.annotation.TransactionScope.SCOPE_NAME;
- public static TransactionBeforeCompletionEvent instance() {
- return INSTANCE;
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ val transactionScope = new StackedTransactionScope();
+ beanFactory.registerScope(SCOPE_NAME, transactionScope);
}
}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectCreator.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectCreator.java
index 4c6d4e90ec..3186cdde8f 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectCreator.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectCreator.java
@@ -78,7 +78,9 @@ interface ObjectCreator {
domainObject.getSpecification().streamAssociations(MixedIn.EXCLUDED)
.forEach(field->field.toDefault(domainObject));
- getPersistenceLifecyclePublisher().onPostCreate(domainObject);
+ if (domainObject.getSpecification().isEntity()) {
+ getPersistenceLifecyclePublisher().onPostCreate(domainObject);
+ }
return domainObject;
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
index 4707fac77a..488f6f3828 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
@@ -32,6 +32,10 @@ import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
* Responsible for collecting and then passing along changes (to the EntityChangeTracker, in persistence commons) so
* that they can be published; and is responsible for calling the various persistence call-back facets.
*
+ * <p>
+ * NOTE: this should really have been called ObjectLifecyclePublisher.
+ * </p>
+ *
* @since 2.0 {index}
*/
public interface ObjectLifecyclePublisher {
diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionBoundaryAware.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/IntrospectionState.java
similarity index 54%
rename from core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionBoundaryAware.java
rename to core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/IntrospectionState.java
index a61d5a5567..094fc4d5d5 100644
--- a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionBoundaryAware.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/specimpl/IntrospectionState.java
@@ -16,26 +16,32 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.causeway.core.interaction.scope;
-
-import org.apache.causeway.applib.services.iactn.Interaction;
-
-public interface TransactionBoundaryAware {
-
- default void beforeEnteringTransactionalBoundary(Interaction interaction) {
-
- }
-
- default void afterEnteringTransactionalBoundary(Interaction interaction, boolean isSynchronizationActive) {
-
- }
-
- default void beforeLeavingTransactionalBoundary(Interaction interaction, boolean isSynchronizationActive) {
-
- }
-
- default void afterLeavingTransactionalBoundary(Interaction interaction) {
-
- }
+package org.apache.causeway.core.metamodel.specloader.specimpl;
+
+public enum IntrospectionState implements Comparable<IntrospectionState> {
+ /**
+ * At this stage, {@link LogicalTypeFacet} only.
+ */
+ NOT_INTROSPECTED,
+
+ /**
+ * Interim stage, to avoid infinite loops while on way to being {@link #TYPE_INTROSPECTED}
+ */
+ TYPE_BEING_INTROSPECTED,
+
+ /**
+ * Type has been introspected (but not its members).
+ */
+ TYPE_INTROSPECTED,
+
+ /**
+ * Interim stage, to avoid infinite loops while on way to being {@link #FULLY_INTROSPECTED}
+ */
+ MEMBERS_BEING_INTROSPECTED,
+
+ /**
+ * Fully introspected... class and also its members.
+ */
+ FULLY_INTROSPECTED
}
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 0db123e2fc..0218c9bd96 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
@@ -24,7 +24,6 @@ import org.springframework.context.annotation.Import;
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.TransactionEventEmitter;
import org.apache.causeway.core.transaction.CausewayModuleCoreTransaction;
import org.apache.causeway.valuetypes.jodatime.integration.CausewayModuleValJodatimeIntegration;
@@ -40,7 +39,6 @@ import org.apache.causeway.valuetypes.jodatime.integration.CausewayModuleValJoda
// @Service's
MetamodelEventService.class,
- TransactionEventEmitter.class,
// @Configuration's
diff --git a/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/TransactionEventEmitter.java b/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/TransactionEventEmitter.java
deleted file mode 100644
index b49a2bba89..0000000000
--- a/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/TransactionEventEmitter.java
+++ /dev/null
@@ -1,67 +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.core.runtime.events;
-
-import javax.inject.Inject;
-
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-
-import org.apache.causeway.applib.services.eventbus.EventBusService;
-import org.apache.causeway.applib.services.iactn.Interaction;
-import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker;
-import org.apache.causeway.core.interaction.scope.TransactionBoundaryAware;
-import org.apache.causeway.core.transaction.events.TransactionAfterCompletionEvent;
-import org.apache.causeway.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(
- final Interaction interaction,
- final boolean isSynchronizationActive) {
- if(isSynchronizationActive) {
- TransactionSynchronizationManager.registerSynchronization(this);
- }
- }
-
-}
diff --git a/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/_Xray.java b/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/_Xray.java
index 697e7ffb01..f1f415e7e1 100644
--- a/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/_Xray.java
+++ b/core/runtime/src/main/java/org/apache/causeway/core/runtime/events/_Xray.java
@@ -29,7 +29,7 @@ import org.apache.causeway.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/module-info.java b/core/runtimeservices/src/main/java/module-info.java
index 69986102e3..33badc9675 100644
--- a/core/runtimeservices/src/main/java/module-info.java
+++ b/core/runtimeservices/src/main/java/module-info.java
@@ -79,7 +79,8 @@ module org.apache.causeway.core.runtimeservices {
requires spring.core;
requires spring.tx;
requires org.apache.causeway.core.codegen.bytebuddy;
+ requires spring.aop;
opens org.apache.causeway.core.runtimeservices.wrapper;
opens org.apache.causeway.core.runtimeservices.wrapper.proxy; //to org.apache.causeway.core.codegen.bytebuddy
-}
\ No newline at end of file
+}
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java
index cd77ffc77c..2f2e256bfd 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java
@@ -26,13 +26,12 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
-import org.apache.causeway.applib.services.iactnlayer.InteractionService;
-
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.TransactionScope;
import org.apache.causeway.applib.services.clock.ClockService;
import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker;
import org.apache.causeway.applib.services.publishing.spi.EntityPropertyChange;
@@ -63,7 +62,6 @@ public class EntityPropertyChangePublisherDefault implements EntityPropertyChang
private final UserService userService;
private final ClockService clockService;
private final TransactionService transactionService;
- private final InteractionService interactionService;
private final InteractionLayerTracker iaTracker;
private final Provider<HasEnlistedEntityPropertyChanges> hasEnlistedEntityPropertyChangesProvider;
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
index b2115fc1ed..a13b5c02e3 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
@@ -31,6 +31,7 @@ import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.TransactionScope;
import org.apache.causeway.applib.services.iactnlayer.InteractionService;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.functional.Either;
@@ -47,6 +48,7 @@ import lombok.RequiredArgsConstructor;
* @since 2.0 {@index}
*/
@Service
+@TransactionScope
@Named(CausewayModuleCoreRuntimeServices.NAMESPACE + ".ObjectLifecyclePublisherDefault")
@Priority(PriorityPrecedence.EARLY)
@Qualifier("Default")
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 b59d004901..7ebe1c7f8e 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
@@ -19,7 +19,6 @@
package org.apache.causeway.core.runtimeservices.session;
import java.io.File;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Stack;
@@ -31,13 +30,13 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
+import org.apache.causeway.core.runtimeservices.transaction.TransactionServiceSpring;
+
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
-import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.services.clock.ClockService;
@@ -60,10 +59,8 @@ 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;
-import org.apache.causeway.core.interaction.integration.InteractionAwareTransactionalBoundaryHandler;
import org.apache.causeway.core.interaction.scope.InteractionScopeBeanFactoryPostProcessor;
import org.apache.causeway.core.interaction.scope.InteractionScopeLifecycleHandler;
-import org.apache.causeway.core.interaction.scope.TransactionBoundaryAware;
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;
@@ -91,30 +88,32 @@ implements
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<Stack<InteractionLayer>> interactionLayerStack = ThreadLocal.withInitial(Stack::new);
final MetamodelEventService runtimeEventService;
final SpecificationLoader specificationLoader;
final ServiceInjector serviceInjector;
- final InteractionAwareTransactionalBoundaryHandler txBoundaryHandler;
final ClockService clockService;
final Provider<CommandPublisher> commandPublisherProvider;
final Provider<TransactionService> transactionServiceProvider;
final ConfigurableBeanFactory beanFactory;
final InteractionScopeLifecycleHandler interactionScopeLifecycleHandler;
+ final TransactionServiceSpring transactionServiceSpring;
+
final InteractionIdGenerator interactionIdGenerator;
- // to allow implementations to have dependencies back on this service.
- @Inject @Lazy List<TransactionBoundaryAware> transactionBoundaryAwareBeans;
@Inject
public InteractionServiceDefault(
final MetamodelEventService runtimeEventService,
final SpecificationLoader specificationLoader,
final ServiceInjector serviceInjector,
- final InteractionAwareTransactionalBoundaryHandler txBoundaryHandler,
+ final TransactionServiceSpring transactionServiceSpring,
final ClockService clockService,
final Provider<CommandPublisher> commandPublisherProvider,
final Provider<TransactionService> transactionServiceProvider,
@@ -123,7 +122,7 @@ implements
this.runtimeEventService = runtimeEventService;
this.specificationLoader = specificationLoader;
this.serviceInjector = serviceInjector;
- this.txBoundaryHandler = txBoundaryHandler;
+ this.transactionServiceSpring = transactionServiceSpring;
this.clockService = clockService;
this.commandPublisherProvider = commandPublisherProvider;
this.transactionServiceProvider = transactionServiceProvider;
@@ -205,7 +204,8 @@ implements
interactionLayerStack.get().push(interactionLayer);
if(isAtTopLevel()) {
- postInteractionOpened(causewayInteraction);
+ transactionServiceSpring.onOpen(causewayInteraction);
+ interactionScopeLifecycleHandler.onTopLevelInteractionOpened();
}
if(log.isDebugEnabled()) {
@@ -355,21 +355,13 @@ implements
return;
}
val interaction = _Casts.<CausewayInteraction>uncheckedCast(stack.get(0).getInteraction());
- txBoundaryHandler.requestRollback(interaction);
+ transactionServiceSpring.requestRollback(interaction);
}
private boolean isAtTopLevel() {
return interactionLayerStack.get().size()==1;
}
- private void postInteractionOpened(final CausewayInteraction interaction) {
- transactionBoundaryAwareBeans.forEach(bean->bean.beforeEnteringTransactionalBoundary(interaction));
- txBoundaryHandler.onOpen(interaction);
- val isSynchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
- transactionBoundaryAwareBeans.forEach(bean->bean.afterEnteringTransactionalBoundary(interaction, isSynchronizationActive));
- interactionScopeLifecycleHandler.onTopLevelInteractionOpened();
- }
-
@SneakyThrows
private void preInteractionClosed(final CausewayInteraction interaction) {
@@ -387,10 +379,8 @@ implements
flushException = e;
}
- val isSynchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
- transactionBoundaryAwareBeans.forEach(bean->bean.beforeLeavingTransactionalBoundary(interaction, isSynchronizationActive));
- txBoundaryHandler.onClose(interaction);
- transactionBoundaryAwareBeans.forEach(bean->bean.afterLeavingTransactionalBoundary(interaction));
+ transactionServiceSpring.onClose(interaction);
+
interactionScopeLifecycleHandler.onTopLevelInteractionPreDestroy(); // cleanup the InteractionScope (Spring scope)
interactionScopeLifecycleHandler.onTopLevelInteractionClosed(); // cleanup the InteractionScope (Spring scope)
interaction.close(); // do this last
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
index ca4552dfbe..af7b7149ac 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
@@ -26,32 +26,42 @@ import java.util.concurrent.atomic.LongAdder;
import javax.annotation.Priority;
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
+import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.event.EventListener;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
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.DefaultTransactionStatus;
+import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
-import org.apache.causeway.applib.services.iactn.Interaction;
import org.apache.causeway.applib.services.iactnlayer.InteractionLayerTracker;
import org.apache.causeway.applib.services.xactn.TransactionId;
import org.apache.causeway.applib.services.xactn.TransactionService;
import org.apache.causeway.applib.services.xactn.TransactionState;
import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.functional.ThrowingRunnable;
import org.apache.causeway.commons.functional.Try;
import org.apache.causeway.commons.internal.base._NullSafe;
+import org.apache.causeway.commons.internal.collections._Lists;
+import org.apache.causeway.commons.internal.debug._Probe;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
-import org.apache.causeway.core.interaction.scope.TransactionBoundaryAware;
+import org.apache.causeway.core.interaction.session.CausewayInteraction;
+import org.apache.causeway.core.runtime.events._Xray;
import org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices;
-import org.apache.causeway.core.transaction.events.TransactionAfterCompletionEvent;
+import org.apache.causeway.core.transaction.events.TransactionCompletionStatus;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.Value;
import lombok.val;
import lombok.extern.log4j.Log4j2;
@@ -70,27 +80,35 @@ import lombok.extern.log4j.Log4j2;
@Log4j2
public class TransactionServiceSpring
implements
- TransactionService,
- TransactionBoundaryAware {
+ TransactionService {
private final Can<PlatformTransactionManager> platformTransactionManagers;
- private final InteractionLayerTracker interactionLayerTracker;
+ private final Provider<InteractionLayerTracker> interactionLayerTrackerProvider;
private final Can<PersistenceExceptionTranslator> persistenceExceptionTranslators;
-
+ private final ConfigurableListableBeanFactory configurableListableBeanFactory;
+// private final Can<TransactionBoundaryAware> transactionBoundaryAwareBeans;
@Inject
public TransactionServiceSpring(
final List<PlatformTransactionManager> platformTransactionManagers,
final List<PersistenceExceptionTranslator> persistenceExceptionTranslators,
- final InteractionLayerTracker interactionLayerTracker) {
+ final Provider<InteractionLayerTracker> interactionLayerTrackerProvider,
+ 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.interactionLayerTrackerProvider = interactionLayerTrackerProvider;
+
+// this.transactionBoundaryAwareBeans = Can.ofCollection(transactionBoundaryAwareBeans);
+// log.info("TransactionBoundaryAwareBeans: {}", transactionBoundaryAwareBeans);
}
// -- SPRING INTEGRATION
@@ -98,23 +116,33 @@ implements
@Override
public <T> Try<T> callTransactional(final TransactionDefinition def, final Callable<T> callable) {
- val txManager = transactionManagerForElseFail(def); // always throws if configuration is wrong
+ val platformTransactionManager = transactionManagerForElseFail(def); // always throws if configuration is wrong
Try<T> result = null;
try {
- val tx = txManager.getTransaction(def);
+ TransactionStatus txStatus = platformTransactionManager.getTransaction(def);
+// if(tx.isNewTransaction()) {
+// transactionBoundaryAwareBeans.forEach(tba -> tba.afterEnteringTransactionalBoundary(platformTransactionManager));
+// }
+ registerTransactionSynchronizations(txStatus);
+
result = Try.call(callable)
- .mapFailure(ex->translateExceptionIfPossible(ex, txManager));
+ .mapFailure(ex->translateExceptionIfPossible(ex, platformTransactionManager));
+// if(tx.isNewTransaction()) {
+// transactionBoundaryAwareBeans.forEach(tba -> tba.beforeLeavingTransactionalBoundary(platformTransactionManager));
+// }
if(result.isFailure()) {
- txManager.rollback(tx);
+ platformTransactionManager.rollback(txStatus);
} else {
- txManager.commit(tx);
+ platformTransactionManager.commit(txStatus);
}
-
+// if(tx.isNewTransaction()) {
+// transactionBoundaryAwareBeans.forEach(tba -> tba.afterLeavingTransactionalBoundary(platformTransactionManager));
+// }
} catch (Exception ex) {
return result!=null
@@ -125,13 +153,30 @@ implements
? result
// return the failure we just catched
- : Try.failure(translateExceptionIfPossible(ex, txManager));
+ : Try.failure(translateExceptionIfPossible(ex, platformTransactionManager));
}
return result;
}
+
+ private void registerTransactionSynchronizations(final TransactionStatus txStatus) {
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ if (txStatus instanceof DefaultTransactionStatus) {
+ configurableListableBeanFactory.getBeansOfType(TransactionSynchronization.class)
+ .values()
+ .stream().filter(AopUtils::isAopProxy) // only the proxies
+ .forEach(TransactionSynchronizationManager::registerSynchronization);
+ } else {
+ configurableListableBeanFactory.getBeansOfType(TransactionSynchronization.class)
+ .values()
+ .stream().filter(AopUtils::isAopProxy) // only the proxies
+ .forEach(TransactionSynchronizationManager::registerSynchronization);
+ }
+ }
+ }
+
// @Override
// public void nextTransaction() {
//
@@ -204,7 +249,7 @@ implements
@Override
public Optional<TransactionId> currentTransactionId() {
- return interactionLayerTracker.getInteractionId()
+ return interactionLayerTrackerProvider.get().getInteractionId()
.map(uuid->{
//XXX get current transaction's persistence context (once we support multiple contexts)
val persistenceContext = "";
@@ -234,25 +279,11 @@ implements
// -- TRANSACTION SEQUENCE TRACKING
+ // TODO: this ThreadLocal (as with all thread-locals) should perhaps somehow be managed using
+ // TransactionSynchronizationManager; see its javadoc for more details and look at implementations of
+ // TransactionSynchronization
private ThreadLocal<LongAdder> txCounter = ThreadLocal.withInitial(LongAdder::new);
- /** INTERACTION BEGIN BOUNDARY */
- @Override
- public void beforeEnteringTransactionalBoundary(final Interaction interaction) {
- txCounter.get().reset();
- }
-
- /** TRANSACTION END BOUNDARY */
- @EventListener(TransactionAfterCompletionEvent.class)
- public void onTransactionEnded(final TransactionAfterCompletionEvent event) {
- txCounter.get().increment();
- }
-
- /** INTERACTION END BOUNDARY */
- @Override
- public void afterLeavingTransactionalBoundary(final Interaction interaction) {
- txCounter.remove(); //XXX not tested yet: can we be certain that no txCounter.get() is called afterwards?
- }
// -- HELPER
@@ -266,8 +297,9 @@ implements
return platformTransactionManagers.getSingleton()
.orElseThrow(()->
platformTransactionManagers.getCardinality().isMultiple()
- ? _Exceptions.illegalState("Multiple PlatformTransactionManagers are configured, "
- + "make sure a PlatformTransactionManager is provided via the TransactionTemplate argument.")
+ ? _Exceptions.illegalState(
+ "Multiple PlatformTransactionManagers are configured, cannot determine which one to use. "
+ + "Instead make sure a PlatformTransactionManager is provided explicitly by passing in a TransactionTemplate (implementation of TransactionDefinition).")
: _Exceptions.illegalState("Needs a PlatformTransactionManager."));
}
@@ -323,4 +355,116 @@ implements
return ex;
}
+
+ public void onOpen(final @NonNull CausewayInteraction interaction) {
+
+ txCounter.get().reset();
+
+ if (log.isDebugEnabled()) {
+ log.debug("opening on {}", _Probe.currentThreadId());
+ }
+
+
+ if (!platformTransactionManagers.isEmpty()) {
+ val onCloseTasks = _Lists.<CloseTask>newArrayList(platformTransactionManagers.size());
+
+ interaction.putAttribute(OnCloseHandle.class, new OnCloseHandle(onCloseTasks));
+
+ platformTransactionManagers.forEach(txManager -> {
+
+ val txDefn = new TransactionTemplate(txManager); // specify the txManager in question
+ txDefn.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
+
+ // either participate in existing or create new transaction
+ TransactionStatus txStatus = txManager.getTransaction(txDefn);
+
+ if(!txStatus.isNewTransaction()) {
+ // we are participating in an exiting transaction (or testing), nothing to do
+ return;
+ }
+ registerTransactionSynchronizations(txStatus);
+
+
+ // we have created a new transaction, so need to provide a CloseTask
+ onCloseTasks.add(
+ new CloseTask(
+ txStatus,
+ txManager.getClass().getName(), // info to be used for display in case of errors
+ () -> {
+// transactionBoundaryAwareBeans.forEach(tbab -> tbab.beforeLeavingTransactionalBoundary(txManager));
+ _Xray.txBeforeCompletion(interactionLayerTrackerProvider.get(), "tx: beforeCompletion");
+ final TransactionCompletionStatus event;
+ if (txStatus.isRollbackOnly()) {
+ txManager.rollback(txStatus);
+ event = TransactionCompletionStatus.ROLLED_BACK;
+ } else {
+ txManager.commit(txStatus);
+ event = TransactionCompletionStatus.COMMITTED;
+ }
+ _Xray.txAfterCompletion(interactionLayerTrackerProvider.get(), String.format("tx: afterCompletion (%s)", event.name()));
+
+// transactionBoundaryAwareBeans.forEach(tbab -> tbab.afterLeavingTransactionalBoundary(txManager));
+ txCounter.get().increment();
+ }
+ )
+ );
+
+ });
+ }
+
+ }
+
+
+ public void requestRollback(final @NonNull CausewayInteraction interaction) {
+ Optional.ofNullable(interaction.getAttribute(OnCloseHandle.class))
+ .ifPresent(OnCloseHandle::requestRollback);
+ }
+
+ public void onClose(final @NonNull CausewayInteraction interaction) {
+
+ if (log.isDebugEnabled()) {
+ log.debug("closing on {}", _Probe.currentThreadId());
+ }
+
+ if (!platformTransactionManagers.isEmpty()) {
+ Optional.ofNullable(interaction.getAttribute(OnCloseHandle.class))
+ .ifPresent(OnCloseHandle::runOnCloseTasks);
+ }
+
+ txCounter.remove(); //XXX not tested yet: can we be certain that no txCounter.get() is called afterwards?
+ }
+
+ @Value
+ private static class CloseTask {
+ @NonNull TransactionStatus txStatus;
+ @NonNull String onErrorInfo;
+ @NonNull ThrowingRunnable runnable;
+ }
+
+ @RequiredArgsConstructor
+ private static class OnCloseHandle {
+ private final @NonNull List<CloseTask> onCloseTasks;
+ void requestRollback() {
+ onCloseTasks.forEach(onCloseTask->{
+ onCloseTask.txStatus.setRollbackOnly();
+ });
+ }
+ void runOnCloseTasks() {
+ onCloseTasks.forEach(onCloseTask->{
+
+ try {
+ onCloseTask.getRunnable().run();
+ } catch(final Throwable ex) {
+ // ignore
+ log.error(
+ "failed to close transactional boundary using transaction-manager {}; "
+ + "continuing to avoid memory leakage",
+ onCloseTask.getOnErrorInfo(),
+ ex);
+ }
+
+ });
+ }
+ }
+
}
diff --git a/core/transaction/src/main/java/module-info.java b/core/transaction/src/main/java/module-info.java
index f3fc966546..d64c8c9e33 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.causeway.core.transaction {
requires spring.context;
requires spring.core;
requires spring.tx;
-}
\ No newline at end of file
+ requires java.transaction;
+}
diff --git a/core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionAfterCompletionEvent.java b/core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionCompletionStatus.java
similarity index 60%
rename from core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionAfterCompletionEvent.java
rename to core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionCompletionStatus.java
index 3e8fbfeb4d..9117afaece 100644
--- a/core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionAfterCompletionEvent.java
+++ b/core/transaction/src/main/java/org/apache/causeway/core/transaction/events/TransactionCompletionStatus.java
@@ -18,13 +18,13 @@
*/
package org.apache.causeway.core.transaction.events;
-import org.springframework.transaction.support.TransactionSynchronization;
+import javax.transaction.Status;
/**
* @since 2.0 {@index}
- * @see TransactionSynchronization
+ * @see Status
*/
-public enum TransactionAfterCompletionEvent {
+public enum TransactionCompletionStatus {
/** Completion status in case of proper commit. */
COMMITTED,
@@ -36,8 +36,29 @@ public enum TransactionAfterCompletionEvent {
UNKNOWN,
;
- public static TransactionAfterCompletionEvent forStatus(int status) {
- return TransactionAfterCompletionEvent.values()[status];
+ /**
+ * @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() {
diff --git a/extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/OutboxRestClient_E2eTest.java b/extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/OutboxRestClient_E2eTest.java
index 77cab60cb2..a9c0166d87 100644
--- a/extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/OutboxRestClient_E2eTest.java
+++ b/extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/OutboxRestClient_E2eTest.java
@@ -49,9 +49,9 @@ public class OutboxRestClient_E2eTest {
.oauthClientSecret("xxx")
.build(),
OutboxClientConfig.builder()
- .pendingUri("services/isis.ext.executionOutbox.OutboxRestApi/actions/pending/invoke")
- .deleteUri("services/isis.ext.executionOutbox.OutboxRestApi/actions/delete/invoke")
- .deleteManyUri("services/isis.ext.executionOutbox.OutboxRestApi/actions/deleteMany/invoke")
+ .pendingUri("services/causeway.ext.executionOutbox.OutboxRestApi/actions/pending/invoke")
+ .deleteUri("services/causeway.ext.executionOutbox.OutboxRestApi/actions/delete/invoke")
+ .deleteManyUri("services/causeway.ext.executionOutbox.OutboxRestApi/actions/deleteMany/invoke")
.build()
);
}
diff --git a/extensions/security/sessionlog/applib/src/main/java/org/apache/causeway/extensions/sessionlog/applib/spiimpl/SessionSubscriberForSessionLog.java b/extensions/security/sessionlog/applib/src/main/java/org/apache/causeway/extensions/sessionlog/applib/spiimpl/SessionSubscriberForSessionLog.java
index a2aace75a7..5881da3d40 100644
--- a/extensions/security/sessionlog/applib/src/main/java/org/apache/causeway/extensions/sessionlog/applib/spiimpl/SessionSubscriberForSessionLog.java
+++ b/extensions/security/sessionlog/applib/src/main/java/org/apache/causeway/extensions/sessionlog/applib/spiimpl/SessionSubscriberForSessionLog.java
@@ -69,18 +69,17 @@ public class SessionSubscriberForSessionLog implements SessionSubscriber {
public void log(final Type type, final String username, final Date date, final CausedBy causedBy, final UUID sessionGuid, final String httpSessionId) {
interactionService.runAnonymous(() -> {
transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> {
+ val sessionLogEntryIfAny = sessionLogEntryRepository.findBySessionGuid(sessionGuid);
if (type == Type.LOGIN) {
- sessionLogEntryRepository.create(username, sessionGuid, httpSessionId, causedBy, Timestamp.from(date.toInstant()));
+ if (sessionLogEntryIfAny.isEmpty()) {
+ sessionLogEntryRepository.create(username, sessionGuid, httpSessionId, causedBy, Timestamp.from(date.toInstant()));
+ }
} else {
-
- val sessionLogEntryIfAny = sessionLogEntryRepository.findBySessionGuid(sessionGuid);
- sessionLogEntryIfAny
- .ifPresent(entry -> {
- entry.setLogoutTimestamp(Timestamp.from(date.toInstant()));
- entry.setCausedBy(causedBy);
- transactionService.flushTransaction();
- }
- );
+ sessionLogEntryIfAny.ifPresent(entry -> {
+ entry.setLogoutTimestamp(Timestamp.from(date.toInstant()));
+ entry.setCausedBy(causedBy);
+ transactionService.flushTransaction();
+ });
}
})
.ifFailureFail(); // throw if rolled back
diff --git a/persistence/commons/src/main/java/module-info.java b/persistence/commons/src/main/java/module-info.java
index ddc1bccd1e..58a535970c 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.causeway.persistence.commons {
requires spring.beans;
requires spring.context;
requires spring.core;
-}
\ No newline at end of file
+ requires spring.tx;
+}
diff --git a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/CausewayModulePersistenceCommons.java b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/CausewayModulePersistenceCommons.java
index 58d7f24b6e..ac62cade36 100644
--- a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/CausewayModulePersistenceCommons.java
+++ b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/CausewayModulePersistenceCommons.java
@@ -32,7 +32,6 @@ import org.apache.causeway.persistence.commons.integration.changetracking.PreAnd
// @Service's
EntityChangeTrackerDefault.class,
- EntityChangeTrackerDefault.TransactionSubscriber.class,
PreAndPostValueEvaluatorServiceDefault.class,
})
public class CausewayModulePersistenceCommons {
diff --git a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
index 6a404701eb..dbf8911f1b 100644
--- a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
+++ b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
@@ -27,26 +27,25 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Function;
-import javax.annotation.Priority;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.event.EventListener;
-import org.springframework.core.annotation.Order;
+import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
-import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.apache.causeway.applib.annotation.DomainObject;
import org.apache.causeway.applib.annotation.EntityChangeKind;
-import org.apache.causeway.applib.annotation.InteractionScope;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.applib.annotation.Programmatic;
+import org.apache.causeway.applib.annotation.TransactionScope;
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.iactn.Interaction;
import org.apache.causeway.applib.services.iactn.InteractionProvider;
-import org.apache.causeway.applib.services.iactnlayer.InteractionService;
import org.apache.causeway.applib.services.metrics.MetricsService;
import org.apache.causeway.applib.services.publishing.spi.EntityChanges;
import org.apache.causeway.applib.services.publishing.spi.EntityPropertyChange;
@@ -68,7 +67,6 @@ import org.apache.causeway.core.transaction.changetracking.EntityChangeTracker;
import org.apache.causeway.core.transaction.changetracking.EntityChangesPublisher;
import org.apache.causeway.core.transaction.changetracking.EntityPropertyChangePublisher;
import org.apache.causeway.core.transaction.changetracking.HasEnlistedEntityChanges;
-import org.apache.causeway.core.transaction.events.TransactionBeforeCompletionEvent;
import lombok.AccessLevel;
import lombok.Getter;
@@ -78,24 +76,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 {@link TransactionScope transaction-scope}d and implements Spring's {@link TransactionSynchronization}
+ * interface, meaning that Spring will call the {@link #beforeCompletion()} callback. This service also implements
+ * {@link org.springframework.core.Ordered} to ensure it isn't called last by {@link TransactionSynchronizationManager}.
* </p>
*
* @since 2.0 {@index}
*/
@Service
+@TransactionScope
@Named("causeway.persistence.commons.EntityChangeTrackerDefault")
-@Priority(PriorityPrecedence.EARLY)
@Qualifier("default")
-@InteractionScope // see note above regarding this
@RequiredArgsConstructor(onConstructor_ = {@Inject})
@Log4j2
public class EntityChangeTrackerDefault
@@ -103,8 +99,15 @@ implements
MetricsService,
EntityChangeTracker,
HasEnlistedEntityPropertyChanges,
- HasEnlistedEntityChanges {
+ HasEnlistedEntityChanges,
+ TransactionSynchronization,
+ Ordered {
+ @Programmatic
+ @Override
+ public int getOrder() {
+ return PriorityPrecedence.EARLY;
+ }
private final EntityPropertyChangePublisher entityPropertyChangePublisher;
private final EntityChangesPublisher entityChangesPublisher;
@@ -193,83 +196,26 @@ implements
return false;
}
- /**
- * 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>
- */
- @Component
- @Named("causeway.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) {
+ @Override
+ public void beforeCompletion() {
try {
- doPublish();
- } finally {
- postPublishing();
- }
- }
+ _Xray.publish(this, interactionProviderProvider);
- private void doPublish() {
- _Xray.publish(this, interactionProviderProvider);
+ log.debug("about to publish entity changes");
+ entityPropertyChangePublisher.publishChangedProperties();
+ entityChangesPublisher.publishChangingEntities(this);
- log.debug("about to publish entity changes");
- entityPropertyChangePublisher.publishChangedProperties();
- entityChangesPublisher.publishChangingEntities(this);
- }
-
- private void postPublishing() {
- log.debug("purging entity change records");
+ } finally {
+ log.debug("purging entity change records");
- enlistedPropertyChangeRecordsById.clear();
- entityPropertyChangeRecordsForPublishing.clear();
+ enlistedPropertyChangeRecordsById.clear();
+ entityPropertyChangeRecordsForPublishing.clear();
- changeKindByEnlistedAdapter.clear();
- entityChangeEventCount.reset();
- numberEntitiesLoaded.reset();
+ changeKindByEnlistedAdapter.clear();
+ entityChangeEventCount.reset();
+ numberEntitiesLoaded.reset();
+ }
}
private void enableCommandPublishing() {
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/CausewayModulePersistenceJdoDatanucleus.java b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/CausewayModulePersistenceJdoDatanucleus.java
index 598f65fb07..faa4da52c1 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/CausewayModulePersistenceJdoDatanucleus.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/CausewayModulePersistenceJdoDatanucleus.java
@@ -27,6 +27,7 @@ import javax.sql.DataSource;
import org.datanucleus.api.jdo.JDOPersistenceManagerFactory;
import org.datanucleus.metadata.PersistenceUnitMetaData;
+
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
index b4285297a5..97f53f6eee 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
@@ -32,6 +32,7 @@ import javax.jdo.PersistenceManager;
import org.datanucleus.api.jdo.JDOQuery;
import org.datanucleus.enhancement.Persistable;
import org.datanucleus.store.rdbms.RDBMSPropertyNames;
+
import org.springframework.lang.Nullable;
import org.apache.causeway.applib.query.AllInstancesQuery;
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
index 9621f025b6..fb80bc4358 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
@@ -43,8 +43,6 @@ import org.apache.causeway.applib.services.xactn.TransactionService;
import org.apache.causeway.commons.internal.base._Refs;
import org.apache.causeway.commons.internal.base._Refs.ObjectReference;
import org.apache.causeway.core.config.presets.CausewayPresets;
-import org.apache.causeway.core.transaction.events.TransactionAfterCompletionEvent;
-import org.apache.causeway.core.transaction.events.TransactionBeforeCompletionEvent;
import org.apache.causeway.testdomain.conf.Configuration_usingJpa;
import org.apache.causeway.testdomain.jpa.JpaTestDomainPersona;
import org.apache.causeway.testdomain.jpa.entities.JpaBook;
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
index ff09d6858f..6d295dd39e 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
@@ -42,8 +42,6 @@ import org.apache.causeway.applib.services.xactn.TransactionService;
import org.apache.causeway.commons.internal.base._Refs;
import org.apache.causeway.commons.internal.base._Refs.ObjectReference;
import org.apache.causeway.core.config.presets.CausewayPresets;
-import org.apache.causeway.core.transaction.events.TransactionAfterCompletionEvent;
-import org.apache.causeway.core.transaction.events.TransactionBeforeCompletionEvent;
import org.apache.causeway.testdomain.conf.Configuration_usingJpa;
import org.apache.causeway.testdomain.jpa.JpaTestDomainPersona;
import org.apache.causeway.testdomain.jpa.entities.JpaBook;
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/conf/Configuration_headless.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/conf/Configuration_headless.java
index c89c965495..29b93681f6 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/conf/Configuration_headless.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/conf/Configuration_headless.java
@@ -36,7 +36,6 @@ import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.services.iactn.Interaction;
import org.apache.causeway.applib.services.metrics.MetricsService;
import org.apache.causeway.core.config.presets.CausewayPresets;
-import org.apache.causeway.core.interaction.scope.TransactionBoundaryAware;
import org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices;
import org.apache.causeway.security.bypass.CausewayModuleSecurityBypass;
import org.apache.causeway.testdomain.util.interaction.DomainObjectTesterFactory;
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/publishing/PublishingTestFactoryAbstract.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/publishing/PublishingTestFactoryAbstract.java
index f279e3696f..46e344f5f2 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/publishing/PublishingTestFactoryAbstract.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/publishing/PublishingTestFactoryAbstract.java
@@ -46,8 +46,6 @@ import org.apache.causeway.commons.internal.debug._Probe;
import org.apache.causeway.commons.internal.debug.xray.XrayModel.Stickiness;
import org.apache.causeway.commons.internal.debug.xray.XrayModel.ThreadMemento;
import org.apache.causeway.commons.internal.debug.xray.XrayUi;
-import org.apache.causeway.core.transaction.events.TransactionAfterCompletionEvent;
-import org.apache.causeway.core.transaction.events.TransactionBeforeCompletionEvent;
import lombok.Getter;
import lombok.NonNull;
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/interaction/InteractionBoundaryProbe.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/interaction/InteractionBoundaryProbe.java
index 4e5304f695..33c97b25e1 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/interaction/InteractionBoundaryProbe.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/interaction/InteractionBoundaryProbe.java
@@ -29,9 +29,6 @@ import org.springframework.stereotype.Service;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.services.iactn.Interaction;
-import org.apache.causeway.core.interaction.scope.TransactionBoundaryAware;
-import org.apache.causeway.core.transaction.events.TransactionAfterCompletionEvent;
-import org.apache.causeway.core.transaction.events.TransactionBeforeCompletionEvent;
import org.apache.causeway.testdomain.util.kv.KVStoreForTesting;
import lombok.val;
diff --git a/scripts/ci/rename-all-published-sources-to-causeway.jsh b/scripts/ci/rename-all-published-sources-to-causeway.jsh
new file mode 100644
index 0000000000..6cde48b619
--- /dev/null
+++ b/scripts/ci/rename-all-published-sources-to-causeway.jsh
@@ -0,0 +1,380 @@
+/*
+ * 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.
+ */
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+/**
+ * Renames project from oldName to newName.
+ *
+ * <p>
+ * This doesn't rewrite the contents of the following (binary) extensions:
+ * <ul>
+ * <li>.jar</li>
+ * <li>.zip</li>
+ * <li>.pptx</li>
+ * <li>.docx</li>
+ * <li>.xlsx</li>
+ * <li>.odt</li>
+ * <li>.rtf</li>
+ * <li>.pdf</li>
+ * </ul>
+ */
+class RenameProject {
+
+ static final boolean DRY_RUN = false;
+
+ // to obtain all the suffixes:
+ // find . -type f | sed -rn 's|.*/[^/]+\.([^/.]+)$|\1|p' | sort -u
+ static final List<String> EXTENSIONS = List.of(
+ "NOTICE",
+ "STATUS",
+ "MF",
+ "TXT",
+ // "adoc", // ignore adoc file content
+ "bat",
+ "cfg",
+ "css",
+ "dcl",
+ "dtd",
+ "factories",
+ "feature",
+ "fxml",
+ "gql",
+ "graphqls",
+ "hbs",
+ "html",
+ "importorder",
+ "info",
+ "ini",
+ "java",
+ "jdo",
+ "js",
+ "json",
+ "kt",
+ "kts",
+ "ldif",
+ "list",
+ "md",
+ "orm",
+ "po",
+ "pot",
+ "properties",
+ "puml",
+ "rdf",
+ "sh",
+ "soc",
+ "svg",
+ "template",
+ "thtml",
+ "ts",
+ "txt",
+ "xml",
+ "xsd",
+ "yaml",
+ "yml"
+ );
+
+ static final List<String> PATH_EXCLUSIONS = List.of(
+ "/build/",
+ "/target/",
+ "/adoc/", // not published in its legacy form
+ "/scripts/ci/", // don't touch
+ "/."
+ );
+
+ static final List<String> UNCONDITIONAL_INCLUSIONS = List.of(
+ "/META-INF/services/");
+
+ public static RenameProject renameBackToLegacy(final File root) {
+ return new RenameProject(root, "isis", "causeway");
+ }
+
+ private RenameProject(final File root, final String oldName, final String newName) {
+ this.root = root;
+ this.fromLower = oldName.toLowerCase();
+ this.toLower = newName.toLowerCase();
+ this.fromUpper = oldName.toUpperCase();
+ this.toUpper = newName.toUpperCase();
+ this.fromTitle = capitalize(fromLower);
+ this.toTitle = capitalize(toLower);
+ }
+
+ final File root;
+
+ final String fromLower;
+ final String toLower;
+ final String fromUpper;
+ final String toUpper;
+ final String fromTitle;
+ final String toTitle;
+
+ static String capitalize(final String s) { return s.substring(0, 1).toUpperCase() + s.substring(1); }
+
+ public void renameAllFiles() throws IOException {
+ Files.find(root.toPath(), Integer.MAX_VALUE, (path, attr)->{
+ if(isPathExcluded(path.toFile())) {
+ return false;
+ }
+ return !attr.isDirectory();
+ }, FileVisitOption.FOLLOW_LINKS)
+ .map(Path::toFile)
+ //.filter(file->fileNameEndsWithSupportedExtension(file)) // rename files unconditionally
+ .forEach(file->{
+ renameIfRequired(file);
+ });
+ }
+
+ private void renameIfRequired(final File file) {
+ var relativeFilePathRenamed = pathRelativeToRoot(file)
+ .replace(fromTitle, toTitle)
+ .replace(fromUpper, toUpper)
+ .replace("\\" + fromLower, "\\" + toLower)
+ .replace("/" + fromLower, "/" + toLower)
+ .replace("-" + fromLower, "-" + toLower)
+ .replace("_" + fromLower, "_" + toLower)
+ .replace("." + fromLower, "." + toLower);
+
+ var filePathRenamed = pathOf(root) + relativeFilePathRenamed;
+
+ if (!filePathRenamed.equals(pathOf(file))) {
+
+ System.err.printf("rename %s -> %s%n", pathRelativeToRoot(file), relativeFilePathRenamed);
+ //System.err.printf("rename %s -> %s%n", pathOf(file), filePathRenamed);
+
+ if(!DRY_RUN) {
+ var fileRename = new File(filePathRenamed);
+ var parentFile = fileRename.getParentFile();
+ parentFile.mkdirs();
+ try {
+ Files.move(file.toPath(), fileRename.toPath());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ public void rewriteAllFileContents() throws IOException {
+
+ Files.find(root.toPath(), Integer.MAX_VALUE, (path, attr)->{
+ return !attr.isDirectory();
+ }, FileVisitOption.FOLLOW_LINKS)
+ .map(Path::toFile)
+ .filter(File::exists)
+ .forEach(file->{
+ if(isPathExcluded(file)) {
+ return;
+ }
+ if(isPathUnconditionallyIncluded(file)
+ || fileNameEndsWithSupportedExtension(file)) {
+ try {
+ rewriteIfRequired(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ private void rewriteIfRequired(final File file) throws FileNotFoundException, IOException {
+ var lines = readLinesFromFile(file, StandardCharsets.UTF_8);
+ var newLines = new ArrayList<String>(lines.size());
+
+ final int linesChangedCount =
+ lines.stream().mapToInt(line->{
+ var newLine = line
+ .replace(fromLower, toLower)
+ .replace(fromUpper, toUpper)
+ .replace(fromTitle, toTitle)
+
+ // update schema declarations in .layout.xml files.
+ // terrible hack - we are assuming the target is 'causeway'
+ // (also, the first arg has to handle the case that we've already converted replaced 'isis' -> 'causeway' throughout...)
+ .replace(
+ "https://causeway.apache.org/applib/layout/menubars/bootstrap3 https://causeway.apache.org/applib/layout/menubars/bootstrap3/menubars.xsd",
+ "http://causeway.apache.org/applib/layout/menubars/bootstrap3 https://isis.apache.org/applib/layout-v1/menubars/bootstrap3/menubars.xsd")
+ .replace(
+ "https://causeway.apache.org/applib/layout/component https://causeway.apache.org/applib/layout/component/component.xsd",
+ "http://causeway.apache.org/applib/layout/component https://isis.apache.org/applib/layout-v1/component/component.xsd")
+ .replace(
+ "https://causeway.apache.org/applib/layout/grid/bootstrap3 https://causeway.apache.org/applib/layout/grid/bootstrap3/bootstrap3.xsd",
+ "http://causeway.apache.org/applib/layout/grid/bootstrap3 https://isis.apache.org/applib/layout-v1/grid/bootstrap3/bootstrap3.xsd")
+ .replace(
+ "https://causeway.apache.org/applib/layout/links https://causeway.apache.org/applib/layout/links/links.xsd",
+ "http://causeway.apache.org/applib/layout/links https://isis.apache.org/applib/layout-v1/links/links.xsd")
+
+ // update namespace declarations in all files (.layout.xml and also constants in .java classes)
+ // (again, the first arg has to handle the case that we've already converted replaced 'isis' -> 'causeway' throughout...)
+ .replace(
+ "\"https://causeway.apache.org/applib/layout/menubars/bootstrap3\"",
+ "\"http://causeway.apache.org/applib/layout/menubars/bootstrap3\"")
+ .replace(
+ "\"https://causeway.apache.org/applib/layout/component\"",
+ "\"http://causeway.apache.org/applib/layout/component\"")
+ .replace(
+ "\"https://causeway.apache.org/applib/layout/grid/bootstrap3\"",
+ "\"http://causeway.apache.org/applib/layout/grid/bootstrap3\"")
+ .replace(
+ "\"https://causeway.apache.org/applib/layout/links",
+ "\"http://causeway.apache.org/applib/layout/links")
+ .replace(
+ "\"https://causeway.apache.org/schema",
+ "\"http://causeway.apache.org/schema")
+
+ ;
+ newLines.add(newLine);
+ return line.equals(newLine)
+ ? 0
+ : 1;
+ })
+ .sum();
+
+ if (linesChangedCount>0) {
+ System.err.printf("rewriting %s%n", pathRelativeToRoot(file));
+ if(!DRY_RUN) {
+ writeLinesToFile(newLines, file, StandardCharsets.UTF_8);
+ }
+ }
+ }
+
+ private static boolean fileNameEndsWithSupportedExtension(final File file) {
+ for (String ext : EXTENSIONS) {
+ if (file.getName().endsWith("." + ext)
+ || file.getName().equals(ext)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isPathUnconditionallyIncluded(final File file) {
+ var path = pathOf(file);
+ for (String incl : UNCONDITIONAL_INCLUSIONS) {
+ if (path.contains(incl)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isPathExcluded(final File file) {
+ var path = pathOf(file);
+// System.out.println(path);
+
+ for (String excl : PATH_EXCLUSIONS) {
+ if (path.contains(excl)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String pathRelativeToRoot(final File file) {
+ var prefix = pathOf(root);
+ var path = pathOf(file);
+ if(!path.startsWith(prefix)) {
+ throw new IllegalArgumentException("file not subpath of root");
+ }
+ return path.substring(prefix.length());
+ }
+
+ private static String pathOf(final File file) {
+ return file.getAbsolutePath().replace('\\', '/');
+ }
+
+ // -- READING
+
+ private static List<String> readLines(
+ final InputStream input,
+ final Charset charset){
+ if(input==null) {
+ return List.of();
+ }
+ var lines = new ArrayList<String>();
+ try(Scanner scanner = new Scanner(input, charset.name())){
+ scanner.useDelimiter("\\n");
+ while(scanner.hasNext()) {
+ var line = scanner.next();
+ //line = line.replace("\r", ""); // preserve windows specific line terminal
+ lines.add(line);
+ }
+ }
+ return lines;
+ }
+
+ private static List<String> readLinesFromFile(
+ final File file,
+ final Charset charset) throws FileNotFoundException, IOException {
+ try(var input = new FileInputStream(file)){
+ return readLines(input, charset);
+ }
+ }
+
+ // -- WRITING
+
+ static void writeLinesToFile(
+ final Iterable<String> lines,
+ final File file,
+ final Charset charset) throws FileNotFoundException, IOException {
+
+ try(var bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset))) {
+ for(var line : lines) {
+ bw.append(line).append("\n");
+ }
+ }
+ }
+
+}
+
+boolean valid = true;
+
+String rootPath = System.getenv("ROOT_PATH_LEGACY");
+if(rootPath.isBlank() || ! new File(rootPath).exists()) {
+ System.err.println("env ROOT_PATH_LEGACY must point to an existing directory");
+ valid = false;
+}
+
+if (valid) {
+ var root = new File(rootPath);
+
+ var renamer = RenameProject.renameBackToLegacy(root);
+ System.out.printf("processing root %s%n", root.getAbsolutePath());
+
+ renamer.renameAllFiles();
+ renamer.rewriteAllFileContents();
+
+ System.out.println("done.");
+}
+
+
+/exit
diff --git a/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/branding/BrandingUiServiceDefault.java b/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/branding/BrandingUiServiceDefault.java
index 7bcc65b6fb..0121ee3a48 100644
--- a/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/branding/BrandingUiServiceDefault.java
+++ b/viewers/commons/services/src/main/java/org/apache/causeway/viewer/commons/services/branding/BrandingUiServiceDefault.java
@@ -43,8 +43,6 @@ implements BrandingUiService {
@Inject
public BrandingUiServiceDefault(final CausewayConfiguration causewayConfiguration) {
- //TODO application name/logo borrowed from Wicket's configuration,
- // we might generalize this config option to all viewers
this.appConfig = causewayConfiguration.getViewer().getCommon().getApplication();
}
diff --git a/viewers/restfulobjects/applib/src/main/resources/META-INF/services/org.apache.causeway.viewer.restfulobjects.applib.client.UriBuilderPlugin b/viewers/restfulobjects/applib/src/main/resources/META-INF/services/org.apache.causeway.viewer.restfulobjects.applib.client.UriBuilderPlugin
deleted file mode 100644
index 055a88de82..0000000000
--- a/viewers/restfulobjects/applib/src/main/resources/META-INF/services/org.apache.causeway.viewer.restfulobjects.applib.client.UriBuilderPlugin
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.causeway.viewer.restfulobjects.jaxrsresteasy4.IsisResteasy4Plugin
\ No newline at end of file
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.java
new file mode 100644
index 0000000000..215c8c4120
--- /dev/null
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.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.viewer.restfulobjects.rendering.service.conneg;
+
+import java.util.List;
+
+import javax.inject.Named;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import org.apache.causeway.applib.annotation.PriorityPrecedence;
+import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
+import org.apache.causeway.core.metamodel.interactions.managed.ManagedCollection;
+import org.apache.causeway.core.metamodel.interactions.managed.ManagedProperty;
+import org.apache.causeway.core.metamodel.object.ManagedObject;
+import org.apache.causeway.viewer.restfulobjects.applib.CausewayModuleViewerRestfulObjectsApplib;
+import org.apache.causeway.viewer.restfulobjects.rendering.IResourceContext;
+import org.apache.causeway.viewer.restfulobjects.rendering.Responses;
+import org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation;
+
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @since 1.x {@index}
+ */
+@Service
+@Named(ContentNegotiationServiceOrgApacheCausewayV1.LOGICAL_TYPE_NAME)
+@javax.annotation.Priority(PriorityPrecedence.MIDPOINT - 200)
+@Qualifier("OrgApacheCausewayV1")
+@Log4j2
+public class ContentNegotiationServiceOrgApacheCausewayV1 extends ContentNegotiationServiceAbstract {
+
+ static final String LOGICAL_TYPE_NAME = CausewayModuleViewerRestfulObjectsApplib.NAMESPACE + ".ContentNegotiationServiceOrgApacheCausewayV1";
+ public static final String ACCEPT_PROFILE = "urn:org.apache.causeway/v1";
+
+ @Override
+ public Response.ResponseBuilder buildResponse(
+ final IResourceContext resourceContext,
+ final ManagedObject objectAdapter) {
+ return whenV1ThenNotImplemented(resourceContext);
+ }
+
+ @Override
+ public Response.ResponseBuilder buildResponse(
+ final IResourceContext resourceContext,
+ final ManagedProperty objectAndProperty) {
+ return whenV1ThenNotImplemented(resourceContext);
+ }
+
+ @Override
+ public Response.ResponseBuilder buildResponse(
+ final IResourceContext resourceContext,
+ final ManagedCollection managedCollection) {
+ return whenV1ThenNotImplemented(resourceContext);
+ }
+
+ @Override
+ public Response.ResponseBuilder buildResponse(
+ final IResourceContext resourceContext,
+ final ManagedAction objectAndAction) {
+ return whenV1ThenNotImplemented(resourceContext);
+ }
+
+ @Override
+ public Response.ResponseBuilder buildResponse(
+ final IResourceContext resourceContext,
+ final ObjectAndActionInvocation objectAndActionInvocation) {
+ return whenV1ThenNotImplemented(resourceContext);
+ }
+
+ // -- HELPER
+
+ private boolean canAccept(final IResourceContext resourceContext) {
+ final List<MediaType> acceptableMediaTypes = resourceContext.getAcceptableMediaTypes();
+ return mediaTypeParameterMatches(acceptableMediaTypes, "profile", ACCEPT_PROFILE);
+ }
+
+ private ResponseBuilder whenV1ThenNotImplemented(final IResourceContext resourceContext) {
+ if(!canAccept(resourceContext)) {
+ return null;
+ }
+ log.warn("profile '{}' is no longer supported use '{}' instead",
+ ACCEPT_PROFILE,
+ ContentNegotiationServiceOrgApacheCausewayV2.ACCEPT_PROFILE);
+ return Responses.ofNotImplemented();
+ }
+
+}
diff --git a/viewers/restfulobjects/viewer/src/main/java/module-info.java b/viewers/restfulobjects/viewer/src/main/java/module-info.java
index 2963adccec..cd5dfa686d 100644
--- a/viewers/restfulobjects/viewer/src/main/java/module-info.java
+++ b/viewers/restfulobjects/viewer/src/main/java/module-info.java
@@ -52,5 +52,4 @@ module org.apache.causeway.viewer.restfulobjects.viewer {
requires spring.context;
requires spring.core;
requires spring.web;
- requires spring.boot;
-}
\ No newline at end of file
+}
(causeway) 04/06: CAUSEWAY-3654: removes accidentally added file
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3654
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 252ada938e4a9cd7309779f453f7aac052a28898
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Dec 12 12:03:11 2023 +0000
CAUSEWAY-3654: removes accidentally added file
---
...ntentNegotiationServiceOrgApacheCausewayV1.java | 108 ---------------------
1 file changed, 108 deletions(-)
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.java
deleted file mode 100644
index 215c8c4120..0000000000
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/causeway/viewer/restfulobjects/rendering/service/conneg/ContentNegotiationServiceOrgApacheCausewayV1.java
+++ /dev/null
@@ -1,108 +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.restfulobjects.rendering.service.conneg;
-
-import java.util.List;
-
-import javax.inject.Named;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
-
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Service;
-
-import org.apache.causeway.applib.annotation.PriorityPrecedence;
-import org.apache.causeway.core.metamodel.interactions.managed.ManagedAction;
-import org.apache.causeway.core.metamodel.interactions.managed.ManagedCollection;
-import org.apache.causeway.core.metamodel.interactions.managed.ManagedProperty;
-import org.apache.causeway.core.metamodel.object.ManagedObject;
-import org.apache.causeway.viewer.restfulobjects.applib.CausewayModuleViewerRestfulObjectsApplib;
-import org.apache.causeway.viewer.restfulobjects.rendering.IResourceContext;
-import org.apache.causeway.viewer.restfulobjects.rendering.Responses;
-import org.apache.causeway.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation;
-
-import lombok.extern.log4j.Log4j2;
-
-/**
- * @since 1.x {@index}
- */
-@Service
-@Named(ContentNegotiationServiceOrgApacheCausewayV1.LOGICAL_TYPE_NAME)
-@javax.annotation.Priority(PriorityPrecedence.MIDPOINT - 200)
-@Qualifier("OrgApacheCausewayV1")
-@Log4j2
-public class ContentNegotiationServiceOrgApacheCausewayV1 extends ContentNegotiationServiceAbstract {
-
- static final String LOGICAL_TYPE_NAME = CausewayModuleViewerRestfulObjectsApplib.NAMESPACE + ".ContentNegotiationServiceOrgApacheCausewayV1";
- public static final String ACCEPT_PROFILE = "urn:org.apache.causeway/v1";
-
- @Override
- public Response.ResponseBuilder buildResponse(
- final IResourceContext resourceContext,
- final ManagedObject objectAdapter) {
- return whenV1ThenNotImplemented(resourceContext);
- }
-
- @Override
- public Response.ResponseBuilder buildResponse(
- final IResourceContext resourceContext,
- final ManagedProperty objectAndProperty) {
- return whenV1ThenNotImplemented(resourceContext);
- }
-
- @Override
- public Response.ResponseBuilder buildResponse(
- final IResourceContext resourceContext,
- final ManagedCollection managedCollection) {
- return whenV1ThenNotImplemented(resourceContext);
- }
-
- @Override
- public Response.ResponseBuilder buildResponse(
- final IResourceContext resourceContext,
- final ManagedAction objectAndAction) {
- return whenV1ThenNotImplemented(resourceContext);
- }
-
- @Override
- public Response.ResponseBuilder buildResponse(
- final IResourceContext resourceContext,
- final ObjectAndActionInvocation objectAndActionInvocation) {
- return whenV1ThenNotImplemented(resourceContext);
- }
-
- // -- HELPER
-
- private boolean canAccept(final IResourceContext resourceContext) {
- final List<MediaType> acceptableMediaTypes = resourceContext.getAcceptableMediaTypes();
- return mediaTypeParameterMatches(acceptableMediaTypes, "profile", ACCEPT_PROFILE);
- }
-
- private ResponseBuilder whenV1ThenNotImplemented(final IResourceContext resourceContext) {
- if(!canAccept(resourceContext)) {
- return null;
- }
- log.warn("profile '{}' is no longer supported use '{}' instead",
- ACCEPT_PROFILE,
- ContentNegotiationServiceOrgApacheCausewayV2.ACCEPT_PROFILE);
- return Responses.ofNotImplemented();
- }
-
-}
(causeway) 02/06: CAUSEWAY-3654: fixes compile issues in tests
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3654
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 3fb0511ef4f9248130deae30d5f9edf936f479cb
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Dec 11 18:29:01 2023 +0000
CAUSEWAY-3654: fixes compile issues in tests
CAUSEWAY-3654: adds a more complete dummy PlatformTransactionManager to AppManifestBase
for integ tests
CAUSEWAY-3654: introduces NoopTransactionSynchronizationService to ensure...
... that there is always at least one transaction-scoped service.
Also moves StackedTransactionScope et al from core-interaction to core-transaction module
CAUSEWAY-3654: fixes some further integ tests
CAUSEWAY-3654: fixes some further integ tests
CAUSEWAY-3654: comments out some tests; sigh
CAUSEWAY-3654: fixes order of wrapping; reenable commented out tests to see what gives
CAUSEWAY-3654: fixes JPA integ test
CAUSEWAY-3654: cleans up javadoc is all
CAUSEWAY-3654: fixes (?) integ test for stable good domain
---
.../interaction/CausewayModuleCoreInteraction.java | 2 -
.../transaction/TransactionServiceSpring.java | 57 ++++++----
core/transaction/src/main/java/module-info.java | 1 +
.../transaction/CausewayModuleCoreTransaction.java | 5 +
.../NoopTransactionSynchronizationService.java | 30 +++++
.../scope/StackedTransactionScope.java | 4 +-
.../TransactionScopeBeanFactoryPostProcessor.java | 2 +-
.../metamodel/PdfjsViewer_Abstract_IntegTest.java | 19 +++-
...elTest_usingBadDomain_noAnnotationEnforced.java | 7 +-
.../DomainModelTest_usingGoodDomain.java | 4 +-
.../integtest/Layout_Counter_IntegTest.java | 20 +++-
.../jdo/JdoExceptionTranslationTest.java | 10 +-
.../testdomain/persistence/jdo/JdoJaxbTest.java | 2 +
.../jdo/JdoTransactionScopeListenerTest.java | 121 ---------------------
.../jpa/JpaExceptionTranslationTest.java | 18 ++-
.../transactions/jpa/CommitListener.java | 78 +++++++++++++
...actionRollbackTest_usingInteractionService.java | 60 +++-------
...actionRollbackTest_usingTransactionService.java | 65 +++--------
.../testdomain/RegressionTestAbstract.java | 15 +--
.../testdomain/conf/Configuration_headless.java | 55 +++-------
.../publishing/PublishingTestFactoryAbstract.java | 31 ++++--
.../util/interaction/InteractionBoundaryProbe.java | 59 ++++++----
22 files changed, 320 insertions(+), 345 deletions(-)
diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/CausewayModuleCoreInteraction.java b/core/interaction/src/main/java/org/apache/causeway/core/interaction/CausewayModuleCoreInteraction.java
index 13ff6e5897..09871d7ce2 100644
--- a/core/interaction/src/main/java/org/apache/causeway/core/interaction/CausewayModuleCoreInteraction.java
+++ b/core/interaction/src/main/java/org/apache/causeway/core/interaction/CausewayModuleCoreInteraction.java
@@ -21,14 +21,12 @@ package org.apache.causeway.core.interaction;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-import org.apache.causeway.core.interaction.scope.TransactionScopeBeanFactoryPostProcessor;
import org.apache.causeway.core.interaction.scope.InteractionScopeBeanFactoryPostProcessor;
@Configuration
@Import({
InteractionScopeBeanFactoryPostProcessor.class,
- TransactionScopeBeanFactoryPostProcessor.class,
})
public class CausewayModuleCoreInteraction {
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
index af7b7149ac..e4fd820bad 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
@@ -28,6 +28,8 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
+import org.apache.causeway.applib.services.iactnlayer.InteractionContext;
+
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -86,7 +88,6 @@ implements
private final Provider<InteractionLayerTracker> interactionLayerTrackerProvider;
private final Can<PersistenceExceptionTranslator> persistenceExceptionTranslators;
private final ConfigurableListableBeanFactory configurableListableBeanFactory;
-// private final Can<TransactionBoundaryAware> transactionBoundaryAwareBeans;
@Inject
public TransactionServiceSpring(
@@ -94,7 +95,6 @@ implements
final List<PersistenceExceptionTranslator> persistenceExceptionTranslators,
final Provider<InteractionLayerTracker> interactionLayerTrackerProvider,
final ConfigurableListableBeanFactory configurableListableBeanFactory
-// , final List<TransactionBoundaryAware> transactionBoundaryAwareBeans
) {
this.platformTransactionManagers = Can.ofCollection(platformTransactionManagers);
@@ -106,12 +106,9 @@ implements
log.info("PersistenceExceptionTranslators: {}", persistenceExceptionTranslators);
this.interactionLayerTrackerProvider = interactionLayerTrackerProvider;
-
-// this.transactionBoundaryAwareBeans = Can.ofCollection(transactionBoundaryAwareBeans);
-// log.info("TransactionBoundaryAwareBeans: {}", transactionBoundaryAwareBeans);
}
- // -- SPRING INTEGRATION
+ // -- API
@Override
public <T> Try<T> callTransactional(final TransactionDefinition def, final Callable<T> callable) {
@@ -123,26 +120,22 @@ implements
try {
TransactionStatus txStatus = platformTransactionManager.getTransaction(def);
-// if(tx.isNewTransaction()) {
-// transactionBoundaryAwareBeans.forEach(tba -> tba.afterEnteringTransactionalBoundary(platformTransactionManager));
-// }
registerTransactionSynchronizations(txStatus);
- result = Try.call(callable)
+ result = Try.call(() -> {
+ final T callResult = callable.call();
+ // we flush here to ensure that the result captures any exception, eg from a declarative constraint violation
+ txStatus.flush();
+ return callResult;
+ })
.mapFailure(ex->translateExceptionIfPossible(ex, platformTransactionManager));
-// if(tx.isNewTransaction()) {
-// transactionBoundaryAwareBeans.forEach(tba -> tba.beforeLeavingTransactionalBoundary(platformTransactionManager));
-// }
if(result.isFailure()) {
platformTransactionManager.rollback(txStatus);
} else {
platformTransactionManager.commit(txStatus);
}
-// if(tx.isNewTransaction()) {
-// transactionBoundaryAwareBeans.forEach(tba -> tba.afterLeavingTransactionalBoundary(platformTransactionManager));
-// }
} catch (Exception ex) {
return result!=null
@@ -152,7 +145,7 @@ implements
// (so we don't shadow the original failure)
? result
- // return the failure we just catched
+ // return the failure we just caught
: Try.failure(translateExceptionIfPossible(ex, platformTransactionManager));
}
@@ -277,6 +270,7 @@ implements
.orElse(TransactionState.NONE);
}
+
// -- TRANSACTION SEQUENCE TRACKING
// TODO: this ThreadLocal (as with all thread-locals) should perhaps somehow be managed using
@@ -285,7 +279,8 @@ implements
private ThreadLocal<LongAdder> txCounter = ThreadLocal.withInitial(LongAdder::new);
- // -- HELPER
+ // -- SPRING INTEGRATION
+
private PlatformTransactionManager transactionManagerForElseFail(final TransactionDefinition def) {
if(def instanceof TransactionTemplate) {
@@ -356,6 +351,12 @@ implements
}
+ /**
+ * For use only by {@link org.apache.causeway.core.runtimeservices.session.InteractionServiceDefault}, sets up
+ * the initial transaction automatically against all available {@link PlatformTransactionManager}s.
+ *
+ * @param interaction The {@link CausewayInteraction} object representing the current interaction.
+ */
public void onOpen(final @NonNull CausewayInteraction interaction) {
txCounter.get().reset();
@@ -391,7 +392,6 @@ implements
txStatus,
txManager.getClass().getName(), // info to be used for display in case of errors
() -> {
-// transactionBoundaryAwareBeans.forEach(tbab -> tbab.beforeLeavingTransactionalBoundary(txManager));
_Xray.txBeforeCompletion(interactionLayerTrackerProvider.get(), "tx: beforeCompletion");
final TransactionCompletionStatus event;
if (txStatus.isRollbackOnly()) {
@@ -403,7 +403,6 @@ implements
}
_Xray.txAfterCompletion(interactionLayerTrackerProvider.get(), String.format("tx: afterCompletion (%s)", event.name()));
-// transactionBoundaryAwareBeans.forEach(tbab -> tbab.afterLeavingTransactionalBoundary(txManager));
txCounter.get().increment();
}
)
@@ -411,15 +410,29 @@ implements
});
}
-
}
+ /**
+ * For use only by {@link org.apache.causeway.core.runtimeservices.session.InteractionServiceDefault}, if
+ * {@link org.apache.causeway.applib.services.iactnlayer.InteractionService#run(InteractionContext, ThrowingRunnable)}
+ * or {@link org.apache.causeway.applib.services.iactnlayer.InteractionService#call(InteractionContext, Callable)}
+ * (or their various overloads) result in an exception.
+ *
+ * @param interaction The {@link CausewayInteraction} object representing the current interaction.
+ */
public void requestRollback(final @NonNull CausewayInteraction interaction) {
Optional.ofNullable(interaction.getAttribute(OnCloseHandle.class))
.ifPresent(OnCloseHandle::requestRollback);
}
+ /**
+ * For use only by {@link org.apache.causeway.core.runtimeservices.session.InteractionServiceDefault}, to close the
+ * transaction initially set up in {@link #onOpen(CausewayInteraction)} against all configured
+ * {@link PlatformTransactionManager}s.
+ *
+ * @param interaction The {@link CausewayInteraction} object representing the current interaction.
+ */
public void onClose(final @NonNull CausewayInteraction interaction) {
if (log.isDebugEnabled()) {
@@ -462,9 +475,7 @@ implements
onCloseTask.getOnErrorInfo(),
ex);
}
-
});
}
}
-
}
diff --git a/core/transaction/src/main/java/module-info.java b/core/transaction/src/main/java/module-info.java
index d64c8c9e33..bec887b514 100644
--- a/core/transaction/src/main/java/module-info.java
+++ b/core/transaction/src/main/java/module-info.java
@@ -21,6 +21,7 @@ module org.apache.causeway.core.transaction {
exports org.apache.causeway.core.transaction.changetracking;
exports org.apache.causeway.core.transaction.changetracking.events;
exports org.apache.causeway.core.transaction.events;
+ exports org.apache.causeway.core.transaction.scope;
requires java.annotation;
requires java.sql;
diff --git a/core/transaction/src/main/java/org/apache/causeway/core/transaction/CausewayModuleCoreTransaction.java b/core/transaction/src/main/java/org/apache/causeway/core/transaction/CausewayModuleCoreTransaction.java
index 21a841b29a..80667e7cf9 100644
--- a/core/transaction/src/main/java/org/apache/causeway/core/transaction/CausewayModuleCoreTransaction.java
+++ b/core/transaction/src/main/java/org/apache/causeway/core/transaction/CausewayModuleCoreTransaction.java
@@ -18,6 +18,9 @@
*/
package org.apache.causeway.core.transaction;
+import org.apache.causeway.core.transaction.scope.NoopTransactionSynchronizationService;
+import org.apache.causeway.core.transaction.scope.TransactionScopeBeanFactoryPostProcessor;
+
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -27,6 +30,8 @@ import org.apache.causeway.core.transaction.changetracking.events.TimestampServi
@Import({
// @Service's
TimestampService.class,
+ TransactionScopeBeanFactoryPostProcessor.class,
+ NoopTransactionSynchronizationService.class,
})
public class CausewayModuleCoreTransaction {
diff --git a/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/NoopTransactionSynchronizationService.java b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/NoopTransactionSynchronizationService.java
new file mode 100644
index 0000000000..afe9036eef
--- /dev/null
+++ b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/NoopTransactionSynchronizationService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.transaction.scope;
+
+import org.apache.causeway.applib.annotation.TransactionScope;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.support.TransactionSynchronization;
+
+/**
+ * This service, which does nothing in and of itself, exists in order to ensure that the {@link StackedTransactionScope}
+ * is always initialized, findinag at least one {@link TransactionScope transaction-scope}d service.
+ */
+@Service
+@TransactionScope
+public class NoopTransactionSynchronizationService implements TransactionSynchronization {
+}
diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/StackedTransactionScope.java b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
similarity index 99%
rename from core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/StackedTransactionScope.java
rename to core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
index 80ca608509..d1a431fb9e 100644
--- a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/StackedTransactionScope.java
+++ b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
@@ -1,4 +1,3 @@
-
/*
* Copyright 2002-2020 the original author or authors.
*
@@ -14,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package org.apache.causeway.core.interaction.scope;
+package org.apache.causeway.core.transaction.scope;
import java.util.HashMap;
import java.util.LinkedHashMap;
diff --git a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/TransactionScopeBeanFactoryPostProcessor.java
similarity index 96%
rename from core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java
rename to core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/TransactionScopeBeanFactoryPostProcessor.java
index 51deb40f56..5e84f9c9c5 100644
--- a/core/interaction/src/main/java/org/apache/causeway/core/interaction/scope/TransactionScopeBeanFactoryPostProcessor.java
+++ b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/TransactionScopeBeanFactoryPostProcessor.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.causeway.core.interaction.scope;
+package org.apache.causeway.core.transaction.scope;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
diff --git a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_Abstract_IntegTest.java b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_Abstract_IntegTest.java
index 8b29374849..4186610697 100644
--- a/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_Abstract_IntegTest.java
+++ b/extensions/vw/pdfjs/metamodel/src/test/java/org/apache/causeway/extensions/pdfjs/metamodel/PdfjsViewer_Abstract_IntegTest.java
@@ -42,6 +42,8 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.AbstractPlatformTransactionManager;
+import org.springframework.transaction.support.DefaultTransactionStatus;
public abstract class PdfjsViewer_Abstract_IntegTest extends CausewayIntegrationTestAbstract {
@@ -60,18 +62,25 @@ public abstract class PdfjsViewer_Abstract_IntegTest extends CausewayIntegration
@Bean
@Singleton
public PlatformTransactionManager platformTransactionManager() {
- return new PlatformTransactionManager() {
+ return new AbstractPlatformTransactionManager() {
@Override
- public void rollback(final TransactionStatus status) throws TransactionException {
+ protected Object doGetTransaction() throws TransactionException {
+ return new Object();
}
@Override
- public TransactionStatus getTransaction(final TransactionDefinition definition) throws TransactionException {
- return null;
+ protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
+
+ }
+
+ @Override
+ protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
+
}
@Override
- public void commit(final TransactionStatus status) throws TransactionException {
+ protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
+
}
};
}
diff --git a/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingBadDomain_noAnnotationEnforced.java b/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingBadDomain_noAnnotationEnforced.java
index 114a4be1cd..96bfa272c5 100644
--- a/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingBadDomain_noAnnotationEnforced.java
+++ b/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingBadDomain_noAnnotationEnforced.java
@@ -20,7 +20,12 @@ package org.apache.causeway.testdomain.domainmodel;
import javax.inject.Inject;
+import org.apache.causeway.testing.integtestsupport.applib.CausewayIntegrationTestAbstract;
+import org.apache.causeway.testing.integtestsupport.applib.CausewayInteractionHandler;
+
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
@@ -54,7 +59,7 @@ import lombok.val;
CausewayPresets.SilenceMetaModel,
CausewayPresets.SilenceProgrammingModel
})
-class DomainModelTest_usingBadDomain_noAnnotationEnforced {
+class DomainModelTest_usingBadDomain_noAnnotationEnforced extends CausewayIntegrationTestAbstract {
@Inject private CausewayConfiguration configuration;
@Inject private CausewaySystemEnvironment causewaySystemEnvironment;
diff --git a/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingGoodDomain.java b/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingGoodDomain.java
index fe2326bd9e..7912d46f55 100644
--- a/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingGoodDomain.java
+++ b/regressiontests/stable-domainmodel/src/test/java/org/apache/causeway/testdomain/domainmodel/DomainModelTest_usingGoodDomain.java
@@ -24,6 +24,8 @@ import java.util.stream.Stream;
import javax.inject.Inject;
+import org.apache.causeway.testing.integtestsupport.applib.CausewayIntegrationTestAbstract;
+
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -130,7 +132,7 @@ import lombok.val;
CausewayPresets.SilenceMetaModel,
CausewayPresets.SilenceProgrammingModel
})
-class DomainModelTest_usingGoodDomain {
+class DomainModelTest_usingGoodDomain extends CausewayIntegrationTestAbstract {
@Inject private MetaModelService metaModelService;
@Inject private JaxbService jaxbService;
diff --git a/regressiontests/stable-layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java b/regressiontests/stable-layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java
index 42512ace30..6ae5018672 100644
--- a/regressiontests/stable-layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java
+++ b/regressiontests/stable-layouts/src/test/java/org/apache/causeway/regressiontests/layouts/integtest/Layout_Counter_IntegTest.java
@@ -69,6 +69,9 @@ import org.apache.causeway.security.bypass.CausewayModuleSecurityBypass;
import org.apache.causeway.testing.integtestsupport.applib.CausewayIntegrationTestAbstract;
import org.apache.causeway.viewer.wicket.applib.CausewayModuleViewerWicketApplibMixins;
+import org.springframework.transaction.support.AbstractPlatformTransactionManager;
+import org.springframework.transaction.support.DefaultTransactionStatus;
+
import lombok.val;
@SpringBootTest(
@@ -94,19 +97,26 @@ public class Layout_Counter_IntegTest extends CausewayIntegrationTestAbstract {
@Bean
@Singleton
public PlatformTransactionManager platformTransactionManager() {
- return new PlatformTransactionManager() {
+ return new AbstractPlatformTransactionManager() {
@Override
- public void rollback(final TransactionStatus status) throws TransactionException {
+ protected Object doGetTransaction() throws TransactionException {
+ return null;
}
@Override
- public TransactionStatus getTransaction(final TransactionDefinition definition) throws TransactionException {
- return null;
+ protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
+
}
@Override
- public void commit(final TransactionStatus status) throws TransactionException {
+ protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
+
+ }
+
+ @Override
+ protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
+
}
};
}
diff --git a/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/persistence/jdo/JdoExceptionTranslationTest.java b/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/persistence/jdo/JdoExceptionTranslationTest.java
index 59ac3c3c2a..2f31e0a0db 100644
--- a/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/persistence/jdo/JdoExceptionTranslationTest.java
+++ b/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/persistence/jdo/JdoExceptionTranslationTest.java
@@ -22,15 +22,16 @@ import java.sql.SQLException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.dao.DataAccessException;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.test.context.TestPropertySources;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.dao.DataAccessException;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.TestPropertySources;
+
import org.apache.causeway.core.config.presets.CausewayPresets;
import org.apache.causeway.testdomain.conf.Configuration_usingJdo;
import org.apache.causeway.testdomain.jdo.RegressionTestWithJdoFixtures;
@@ -49,7 +50,6 @@ import lombok.val;
@TestPropertySources({
@TestPropertySource(CausewayPresets.UseLog4j2Test)
})
-//@Transactional ... we manage transaction ourselves
class JdoExceptionTranslationTest extends RegressionTestWithJdoFixtures {
@BeforeAll
diff --git a/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/persistence/jdo/JdoJaxbTest.java b/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/persistence/jdo/JdoJaxbTest.java
index cf57499752..2ebefdc81a 100644
--- a/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/persistence/jdo/JdoJaxbTest.java
+++ b/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/persistence/jdo/JdoJaxbTest.java
@@ -20,6 +20,7 @@ package org.apache.causeway.testdomain.persistence.jdo;
import javax.inject.Inject;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
@@ -45,6 +46,7 @@ class JdoJaxbTest extends RegressionTestWithJdoFixtures {
@Inject private JaxbService jaxbService;
+ // @Disabled // CAUSEWAY-3654
@Test
void inventoryJaxbVm_shouldRoundtripProperly() {
diff --git a/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/transactions/jdo/JdoTransactionScopeListenerTest.java b/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/transactions/jdo/JdoTransactionScopeListenerTest.java
deleted file mode 100644
index e0f6eb3b9d..0000000000
--- a/regressiontests/stable-persistence-jdo/src/test/java/org/apache/causeway/testdomain/transactions/jdo/JdoTransactionScopeListenerTest.java
+++ /dev/null
@@ -1,121 +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.testdomain.transactions.jdo;
-
-import javax.inject.Inject;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.TestPropertySource;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.apache.causeway.applib.services.iactnlayer.InteractionService;
-import org.apache.causeway.applib.services.repository.RepositoryService;
-import org.apache.causeway.applib.services.xactn.TransactionService;
-import org.apache.causeway.core.config.presets.CausewayPresets;
-import org.apache.causeway.testdomain.conf.Configuration_usingJdo;
-import org.apache.causeway.testdomain.fixtures.EntityTestFixtures;
-import org.apache.causeway.testdomain.jdo.JdoTestFixtures;
-import org.apache.causeway.testdomain.jdo.entities.JdoBook;
-import org.apache.causeway.testdomain.util.interaction.InteractionBoundaryProbe;
-import org.apache.causeway.testdomain.util.kv.KVStoreForTesting;
-
-@SpringBootTest(
- classes = {
- Configuration_usingJdo.class,
- InteractionBoundaryProbe.class
- },
- properties = {
- "spring.datasource.url=jdbc:h2:mem:JdoTransactionScopeListenerTest"
- })
-//@Transactional
-@TestPropertySource(CausewayPresets.UseLog4j2Test)
-/**
- * With this test we manage CausewayInteractions ourselves. (not sub-classing CausewayIntegrationTestAbstract)
- */
-class JdoTransactionScopeListenerTest {
-
- @Inject private JdoTestFixtures jdoTestFixtures;
- @Inject private TransactionService transactionService;
- @Inject private RepositoryService repository;
- @Inject private InteractionService interactionService;
- @Inject private KVStoreForTesting kvStoreForTesting;
- private EntityTestFixtures.Lock lock;
-
- /* Expectations:
- * 1. for each InteractionScope there should be a new InteractionBoundaryProbe instance
- * 2. for each Transaction the current InteractionBoundaryProbe should get notified
- *
- * first we have 1 InteractionScope with 1 expected Transaction during 'setUp'
- * then we have 1 InteractionScope with 3 expected Transactions within the test method
- *
- */
-
- @BeforeEach
- void setUp() {
- // clear repository
- lock = jdoTestFixtures.aquireLockAndClear();
- }
-
- @AfterEach
- void cleanUp() {
- // clear repository
- lock.release();
- }
-
- @Test
- void sessionScopedProbe_shouldBeReused_andBeAwareofTransactionBoundaries() {
-
- assertEquals(0, InteractionBoundaryProbe.totalInteractionsStarted(kvStoreForTesting));
- assertEquals(0, InteractionBoundaryProbe.totalInteractionsEnded(kvStoreForTesting));
- assertEquals(0, InteractionBoundaryProbe.totalTransactionsEnding(kvStoreForTesting));
- assertEquals(0, InteractionBoundaryProbe.totalTransactionsCommitted(kvStoreForTesting));
-
- // new InteractionScope with a new transaction (#1)
- interactionService.runAnonymous(()->{
-
- // expected pre condition
- // reuse transaction (#1)
- assertEquals(0, repository.allInstances(JdoBook.class).size());
-
- // reuse transaction (#1)
- transactionService.runWithinCurrentTransactionElseCreateNew(()->{
- // + 1 interaction + 1 transaction
- jdoTestFixtures.add3Books();
- })
- .ifFailureFail();
-
- // expected post condition
- // reuse transaction (#1)
- assertEquals(3, repository.allInstances(JdoBook.class).size());
-
- });
-
- assertEquals(1, InteractionBoundaryProbe.totalInteractionsStarted(kvStoreForTesting));
- assertEquals(1, InteractionBoundaryProbe.totalInteractionsEnded(kvStoreForTesting));
- assertEquals(1, InteractionBoundaryProbe.totalTransactionsEnding(kvStoreForTesting));
- assertEquals(1, InteractionBoundaryProbe.totalTransactionsCommitted(kvStoreForTesting));
-
- }
-
-
-}
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/JpaExceptionTranslationTest.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/JpaExceptionTranslationTest.java
index ed487ed662..f8afe0c294 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/JpaExceptionTranslationTest.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/persistence/jpa/JpaExceptionTranslationTest.java
@@ -22,15 +22,17 @@ import java.sql.SQLException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.dao.DataAccessException;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.test.context.TestPropertySources;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.TestPropertySources;
+
import org.apache.causeway.core.config.presets.CausewayPresets;
import org.apache.causeway.testdomain.conf.Configuration_usingJpa;
import org.apache.causeway.testdomain.jpa.RegressionTestWithJpaFixtures;
@@ -44,12 +46,11 @@ import lombok.val;
},
properties = {
"spring.datasource.url=jdbc:h2:mem:JpaExceptionTranslationTest",
- })
+ }
+)
@TestPropertySources({
@TestPropertySource(CausewayPresets.UseLog4j2Test)
})
-//@Transactional ... we manage transaction ourselves
-//@DirtiesContext
class JpaExceptionTranslationTest extends RegressionTestWithJpaFixtures {
@BeforeAll
@@ -61,7 +62,6 @@ class JpaExceptionTranslationTest extends RegressionTestWithJpaFixtures {
@Test
void booksUniqueByIsbn_whenViolated_shouldThrowTranslatedException() {
-
// when adding a book for which one with same ISBN already exists in the database,
// we expect to see a Spring recognized DataAccessException been thrown
@@ -102,7 +102,5 @@ class JpaExceptionTranslationTest extends RegressionTestWithJpaFixtures {
testFixtures.assertInventoryHasBooks(inventory.getProducts(), 1, 2, 3);
});
-
}
-
}
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/CommitListener.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/CommitListener.java
new file mode 100644
index 0000000000..804b372519
--- /dev/null
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/CommitListener.java
@@ -0,0 +1,78 @@
+/*
+ * 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.testdomain.transactions.jpa;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import org.apache.causeway.applib.annotation.TransactionScope;
+import org.apache.causeway.applib.annotation.Value;
+import org.apache.causeway.core.transaction.events.TransactionCompletionStatus;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.support.TransactionSynchronization;
+
+@Service
+@TransactionScope
+public class CommitListener implements TransactionSynchronization {
+
+
+ @Value
+ @RequiredArgsConstructor
+ public static class TransactionAfterCompletionEvent {
+ final TransactionCompletionStatus transactionCompletionStatus;
+ }
+
+// /** transaction end boundary (pre) */
+// @EventListener(TransactionBeforeCompletionEvent.class)
+// public void onPreCompletion(final TransactionBeforeCompletionEvent event) {
+// //_Probe.errOut("=== TRANSACTION before completion");
+// }
+
+
+// /** transaction end boundary (post) */
+// @EventListener(TransactionAfterCompletionEvent.class)
+// public void onPostCompletion(final TransactionAfterCompletionEvent event) {...}
+
+ @Override
+ public void afterCompletion(int status) {
+ TransactionCompletionStatus transactionCompletionStatus = TransactionCompletionStatus.forStatus(status);
+ TransactionAfterCompletionEvent event = new TransactionAfterCompletionEvent(transactionCompletionStatus);
+ //_Probe.errOut("=== TRANSACTION after completion (%s)", event.name());
+ Optional.ofNullable(listener)
+ .ifPresent(li -> {
+ li.accept(event);
+ unbind();
+ });
+ }
+
+ private Consumer<TransactionAfterCompletionEvent> listener;
+
+ void bind(final @NonNull Consumer<TransactionAfterCompletionEvent> listener) {
+ this.listener = listener;
+ }
+
+ void unbind() {
+ this.listener = null;
+ }
+
+}
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
index fb80bc4358..e597e07e72 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingInteractionService.java
@@ -23,6 +23,10 @@ import java.util.function.Consumer;
import javax.inject.Inject;
+import org.apache.causeway.applib.annotation.TransactionScope;
+
+import org.apache.causeway.core.transaction.events.TransactionCompletionStatus;
+
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -49,13 +53,15 @@ import org.apache.causeway.testdomain.jpa.entities.JpaBook;
import org.apache.causeway.testing.fixtures.applib.fixturescripts.FixtureScripts;
import org.apache.causeway.testing.integtestsupport.applib.CausewayInteractionHandler;
+import org.springframework.transaction.support.TransactionSynchronization;
+
import lombok.NonNull;
import lombok.val;
@SpringBootTest(
classes = {
Configuration_usingJpa.class,
- JpaTransactionRollbackTest_usingInteractionService.CommitListener.class
+ CommitListener.class
},
properties = {
"spring.datasource.url=jdbc:h2:mem:JpaTransactionRollbackTest_usingInteractionService",
@@ -76,7 +82,7 @@ class JpaTransactionRollbackTest_usingInteractionService
@Inject private RepositoryService repository;
@Inject private CommitListener commitListener;
- private ObjectReference<TransactionAfterCompletionEvent> transactionAfterCompletionEvent;
+ private ObjectReference<CommitListener.TransactionAfterCompletionEvent> transactionAfterCompletionEvent;
@BeforeEach
void setUp() {
@@ -84,8 +90,7 @@ class JpaTransactionRollbackTest_usingInteractionService
// cleanup
fixtureScripts.runPersona(JpaTestDomainPersona.InventoryPurgeAll);
- transactionAfterCompletionEvent =
- _Refs.<TransactionAfterCompletionEvent>objectRef(null);
+ transactionAfterCompletionEvent = _Refs.objectRef(null);
}
@AfterEach
@@ -109,8 +114,8 @@ class JpaTransactionRollbackTest_usingInteractionService
});
assertEquals(
- TransactionAfterCompletionEvent.COMMITTED,
- transactionAfterCompletionEvent.getValueElseDefault(null));
+ TransactionCompletionStatus.COMMITTED,
+ transactionAfterCompletionEvent.getValue().map(x -> x.transactionCompletionStatus).orElse(null));
transactionService.runWithinCurrentTransactionElseCreateNew(()->{
@@ -144,8 +149,8 @@ class JpaTransactionRollbackTest_usingInteractionService
assertTrue(result.isFailure());
assertEquals(
- TransactionAfterCompletionEvent.ROLLED_BACK,
- transactionAfterCompletionEvent.getValueElseDefault(null));
+ TransactionCompletionStatus.ROLLED_BACK,
+ transactionAfterCompletionEvent.getValue().map(x -> x.transactionCompletionStatus).orElse(null));
transactionService.runWithinCurrentTransactionElseCreateNew(()->{
@@ -190,9 +195,9 @@ class JpaTransactionRollbackTest_usingInteractionService
// interactionService detects whether a rollback was requested and does not throw in such a case
assertTrue(result.isSuccess());
- val actualEvent = transactionAfterCompletionEvent.getValueElseDefault(null);
- assertTrue(
- actualEvent == TransactionAfterCompletionEvent.ROLLED_BACK);
+ assertEquals(
+ TransactionCompletionStatus.ROLLED_BACK,
+ transactionAfterCompletionEvent.getValue().map(x -> x.transactionCompletionStatus).orElse(null));
transactionService.runWithinCurrentTransactionElseCreateNew(()->{
@@ -204,37 +209,4 @@ class JpaTransactionRollbackTest_usingInteractionService
// -- HELPER
- @Service
- public static class CommitListener {
-
- /** transaction end boundary (pre) */
- @EventListener(TransactionBeforeCompletionEvent.class)
- public void onPreCompletion(final TransactionBeforeCompletionEvent event) {
- //_Probe.errOut("=== TRANSACTION before completion");
- }
-
- /** transaction end boundary (post) */
- @EventListener(TransactionAfterCompletionEvent.class)
- public void onPostCompletion(final TransactionAfterCompletionEvent event) {
- //_Probe.errOut("=== TRANSACTION after completion (%s)", event.name());
- Optional.ofNullable(listener)
- .ifPresent(li->{
- li.accept(event);
- unbind();
- });
- }
-
- private Consumer<TransactionAfterCompletionEvent> listener;
-
- void bind(final @NonNull Consumer<TransactionAfterCompletionEvent> listener) {
- this.listener = listener;
- }
-
- void unbind() {
- this.listener = null;
- }
-
- }
-
-
}
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
index 6d295dd39e..1c3af590a9 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/causeway/testdomain/transactions/jpa/JpaTransactionRollbackTest_usingTransactionService.java
@@ -18,19 +18,18 @@
*/
package org.apache.causeway.testdomain.transactions.jpa;
-import java.util.Optional;
-import java.util.function.Consumer;
-
import javax.inject.Inject;
+import org.apache.causeway.commons.internal.base._Refs;
+
+import org.apache.causeway.core.transaction.events.TransactionCompletionStatus;
+
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.context.event.EventListener;
-import org.springframework.stereotype.Service;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;
@@ -39,8 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.causeway.applib.services.repository.RepositoryService;
import org.apache.causeway.applib.services.xactn.TransactionService;
-import org.apache.causeway.commons.internal.base._Refs;
-import org.apache.causeway.commons.internal.base._Refs.ObjectReference;
import org.apache.causeway.core.config.presets.CausewayPresets;
import org.apache.causeway.testdomain.conf.Configuration_usingJpa;
import org.apache.causeway.testdomain.jpa.JpaTestDomainPersona;
@@ -48,13 +45,12 @@ import org.apache.causeway.testdomain.jpa.entities.JpaBook;
import org.apache.causeway.testing.fixtures.applib.fixturescripts.FixtureScripts;
import org.apache.causeway.testing.integtestsupport.applib.CausewayInteractionHandler;
-import lombok.NonNull;
import lombok.val;
@SpringBootTest(
classes = {
Configuration_usingJpa.class,
- JpaTransactionRollbackTest_usingTransactionService.CommitListener.class
+ CommitListener.class
},
properties = {
"spring.datasource.url=jdbc:h2:mem:JpaTransactionRollbackTest_usingTransactionService",
@@ -74,7 +70,7 @@ class JpaTransactionRollbackTest_usingTransactionService
@Inject private RepositoryService repository;
@Inject private CommitListener commitListener;
- private ObjectReference<TransactionAfterCompletionEvent> transactionAfterCompletionEvent;
+ private _Refs.ObjectReference<CommitListener.TransactionAfterCompletionEvent> transactionAfterCompletionEvent;
@BeforeEach
void setUp() {
@@ -82,8 +78,7 @@ class JpaTransactionRollbackTest_usingTransactionService
// cleanup
fixtureScripts.runPersona(JpaTestDomainPersona.InventoryPurgeAll);
- transactionAfterCompletionEvent =
- _Refs.<TransactionAfterCompletionEvent>objectRef(null);
+ transactionAfterCompletionEvent = _Refs.objectRef(null);
}
@AfterEach
@@ -107,8 +102,8 @@ class JpaTransactionRollbackTest_usingTransactionService
});
assertEquals(
- TransactionAfterCompletionEvent.COMMITTED,
- transactionAfterCompletionEvent.getValueElseDefault(null));
+ TransactionCompletionStatus.COMMITTED,
+ transactionAfterCompletionEvent.getValue().map(x -> x.transactionCompletionStatus).orElse(null));
transactionService.runWithinCurrentTransactionElseCreateNew(()->{
@@ -142,8 +137,8 @@ class JpaTransactionRollbackTest_usingTransactionService
assertTrue(result.isFailure());
assertEquals(
- TransactionAfterCompletionEvent.ROLLED_BACK,
- transactionAfterCompletionEvent.getValueElseDefault(null));
+ TransactionCompletionStatus.ROLLED_BACK,
+ transactionAfterCompletionEvent.getValue().map(x -> x.transactionCompletionStatus).orElse(null));
transactionService.runWithinCurrentTransactionElseCreateNew(()->{
@@ -187,10 +182,10 @@ class JpaTransactionRollbackTest_usingTransactionService
assertTrue(result.isFailure());
- val actualEvent = transactionAfterCompletionEvent.getValueElseDefault(null);
+ val actualEvent = transactionAfterCompletionEvent.getValue().map(x -> x.transactionCompletionStatus).orElse(null);
assertTrue(
- actualEvent == TransactionAfterCompletionEvent.ROLLED_BACK
- || actualEvent == TransactionAfterCompletionEvent.UNKNOWN);
+ actualEvent == TransactionCompletionStatus.ROLLED_BACK
+ || actualEvent == TransactionCompletionStatus.UNKNOWN);
transactionService.runWithinCurrentTransactionElseCreateNew(()->{
@@ -202,37 +197,5 @@ class JpaTransactionRollbackTest_usingTransactionService
// -- HELPER
- @Service
- public static class CommitListener {
-
- /** transaction end boundary (pre) */
- @EventListener(TransactionBeforeCompletionEvent.class)
- public void onPreCompletion(final TransactionBeforeCompletionEvent event) {
- //_Probe.errOut("=== TRANSACTION before completion");
- }
-
- /** transaction end boundary (post) */
- @EventListener(TransactionAfterCompletionEvent.class)
- public void onPostCompletion(final TransactionAfterCompletionEvent event) {
- //_Probe.errOut("=== TRANSACTION after completion (%s)", event.name());
- Optional.ofNullable(listener)
- .ifPresent(li->{
- li.accept(event);
- unbind();
- });
- }
-
- private Consumer<TransactionAfterCompletionEvent> listener;
-
- void bind(final @NonNull Consumer<TransactionAfterCompletionEvent> listener) {
- this.listener = listener;
- }
-
- void unbind() {
- this.listener = null;
- }
-
- }
-
}
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/RegressionTestAbstract.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/RegressionTestAbstract.java
index 43f0fa1d31..1c6a3dd564 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/RegressionTestAbstract.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/RegressionTestAbstract.java
@@ -22,6 +22,8 @@ import java.util.concurrent.Callable;
import javax.inject.Inject;
+import org.apache.causeway.testing.integtestsupport.applib.CausewayIntegrationTestAbstract;
+
import org.springframework.transaction.annotation.Propagation;
import org.apache.causeway.applib.services.bookmark.BookmarkService;
@@ -40,16 +42,15 @@ import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
public abstract class RegressionTestAbstract {
protected void run(final ThrowingRunnable runnable) {
- transactionService.runTransactional(Propagation.REQUIRES_NEW, ()->
- interactionService.runAnonymous(runnable))
- .ifFailureFail();
+ interactionService.runAnonymous(() ->
+ transactionService.runTransactional(Propagation.REQUIRES_NEW, runnable).ifFailureFail()
+ );
}
protected <T> T call(final Callable<T> callable) {
- return transactionService.callTransactional(Propagation.REQUIRES_NEW, ()->
- interactionService.callAnonymous(callable))
- // assuming return value of callable is not nullable
- .valueAsNonNullElseFail();
+ return interactionService.callAnonymous(() ->
+ transactionService.callTransactional(Propagation.REQUIRES_NEW, callable))
+ .valueAsNonNullElseFail();
}
// -- ASSERTIONS
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/conf/Configuration_headless.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/conf/Configuration_headless.java
index 29b93681f6..2378fd7e87 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/conf/Configuration_headless.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/conf/Configuration_headless.java
@@ -21,6 +21,8 @@ package org.apache.causeway.testdomain.conf;
import javax.inject.Inject;
import javax.inject.Singleton;
+import org.apache.causeway.applib.annotation.TransactionScope;
+
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -41,13 +43,16 @@ import org.apache.causeway.security.bypass.CausewayModuleSecurityBypass;
import org.apache.causeway.testdomain.util.interaction.DomainObjectTesterFactory;
import org.apache.causeway.testdomain.util.kv.KVStoreForTesting;
+import org.springframework.transaction.support.AbstractPlatformTransactionManager;
+import org.springframework.transaction.support.DefaultTransactionStatus;
+import org.springframework.transaction.support.TransactionSynchronization;
+
import lombok.RequiredArgsConstructor;
@Configuration
@Import({
CausewayModuleCoreRuntimeServices.class,
CausewayModuleSecurityBypass.class,
- Configuration_headless.HeadlessCommandSupport.class,
KVStoreForTesting.class, // Helper for JUnit Tests
DomainObjectTesterFactory.class // Helper for JUnit Tests
})
@@ -56,53 +61,29 @@ import lombok.RequiredArgsConstructor;
})
public class Configuration_headless {
- @Service
- @javax.annotation.Priority(PriorityPrecedence.MIDPOINT)
- @RequiredArgsConstructor(onConstructor_ = {@Inject})
- public static class HeadlessCommandSupport
- implements TransactionBoundaryAware {
-
- @Override
- public void beforeEnteringTransactionalBoundary(final Interaction interaction) {
-// _Probe.errOut("Interaction HAS_STARTED conversationId=%s", interaction.getInteractionId());
- setupCommandCreateIfMissing();
- }
-
- @Override
- public void afterLeavingTransactionalBoundary(final Interaction interaction) {
-// _Probe.errOut("Interaction IS_ENDING conversationId=%s", interaction.getInteractionId());
- }
-
- public void setupCommandCreateIfMissing() {
-
-// val interactionProvider = interactionProviderProvider.get();
-// @SuppressWarnings("unused")
-// final Interaction interaction = Optional.ofNullable(interactionContext.getInteraction())
-// .orElseGet(()->{
-// val newCommand = new Command();
-// val newInteraction = new Interaction(newCommand);
-// interactionProvider.setInteraction(newInteraction);
-// return newInteraction;
-// });
- }
-
- }
@Bean @Singleton
public PlatformTransactionManager platformTransactionManager() {
- return new PlatformTransactionManager() {
+ return new AbstractPlatformTransactionManager() {
@Override
- public void rollback(final TransactionStatus status) throws TransactionException {
+ protected Object doGetTransaction() throws TransactionException {
+ return null;
}
@Override
- public TransactionStatus getTransaction(final TransactionDefinition definition) throws TransactionException {
- return null;
+ protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
+
+ }
+
+ @Override
+ protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
+
}
@Override
- public void commit(final TransactionStatus status) throws TransactionException {
+ protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
+
}
};
}
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/publishing/PublishingTestFactoryAbstract.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/publishing/PublishingTestFactoryAbstract.java
index 46e344f5f2..3a21164369 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/publishing/PublishingTestFactoryAbstract.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/publishing/PublishingTestFactoryAbstract.java
@@ -24,6 +24,10 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
+import org.apache.causeway.applib.annotation.TransactionScope;
+
+import org.apache.causeway.core.transaction.events.TransactionCompletionStatus;
+
import org.junit.jupiter.api.DynamicTest;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
@@ -47,6 +51,8 @@ import org.apache.causeway.commons.internal.debug.xray.XrayModel.Stickiness;
import org.apache.causeway.commons.internal.debug.xray.XrayModel.ThreadMemento;
import org.apache.causeway.commons.internal.debug.xray.XrayUi;
+import org.springframework.transaction.support.TransactionSynchronization;
+
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@@ -158,13 +164,17 @@ public abstract class PublishingTestFactoryAbstract {
* to, until the end of the test's exclusive transaction.
*/
@Service
- public static class CommitListener {
+ @TransactionScope
+ public static class CommitListener implements TransactionSynchronization {
private PublishingTestContext testContext;
- /** transaction end boundary (pre) */
- @EventListener(TransactionBeforeCompletionEvent.class)
- public void onPreCompletion(final TransactionBeforeCompletionEvent event) {
+// /** transaction end boundary (pre) */
+// @EventListener(TransactionBeforeCompletionEvent.class)
+// public void onPreCompletion(final TransactionBeforeCompletionEvent event) {
+
+ @Override
+ public void beforeCompletion() {
_Probe.errOut("=== TRANSACTION before completion");
if(testContext!=null) {
testContext.getTraceLog().log("3.1 pre-commit event is occurring");
@@ -172,13 +182,18 @@ public abstract class PublishingTestFactoryAbstract {
}
}
- /** transaction end boundary (post) */
- @EventListener(TransactionAfterCompletionEvent.class)
- public void onPostCompletion(final TransactionAfterCompletionEvent event) {
+// /** transaction end boundary (post) */
+// @EventListener(TransactionAfterCompletionEvent.class)
+// public void onPostCompletion(final TransactionAfterCompletionEvent event) {
+
+ @Override
+ public void afterCompletion(int status) {
+
+ TransactionCompletionStatus transactionCompletionStatus = TransactionCompletionStatus.forStatus(status);
_Probe.errOut("=== TRANSACTION after completion");
if(testContext!=null) {
try {
- if(event.isCommitted()) {
+ if(transactionCompletionStatus.isCommitted()) {
testContext.getTraceLog().log("3.2 post-commit event is occurring");
testContext.runVerify(VerificationStage.POST_COMMIT);
} else {
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/interaction/InteractionBoundaryProbe.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/interaction/InteractionBoundaryProbe.java
index 33c97b25e1..b097e181e1 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/interaction/InteractionBoundaryProbe.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/interaction/InteractionBoundaryProbe.java
@@ -22,6 +22,10 @@ import java.util.function.Supplier;
import javax.inject.Inject;
+import org.apache.causeway.applib.annotation.TransactionScope;
+
+import org.apache.causeway.core.transaction.events.TransactionCompletionStatus;
+
import org.junit.jupiter.api.Assertions;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
@@ -31,42 +35,56 @@ import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.services.iactn.Interaction;
import org.apache.causeway.testdomain.util.kv.KVStoreForTesting;
+import org.springframework.transaction.support.TransactionSynchronization;
+
import lombok.val;
import lombok.extern.log4j.Log4j2;
@Service
+@TransactionScope
@Log4j2
-public class InteractionBoundaryProbe implements TransactionBoundaryAware {
+public class InteractionBoundaryProbe implements TransactionSynchronization {
@Inject private KVStoreForTesting kvStoreForTesting;
- /** INTERACTION BEGIN BOUNDARY */
- @Override
- public void beforeEnteringTransactionalBoundary(Interaction interaction) {
- log.debug("iaStarted");
- kvStoreForTesting.incrementCounter(InteractionBoundaryProbe.class, "iaStarted");
- }
+// /** INTERACTION BEGIN BOUNDARY */
+// @Override
+// public void beforeEnteringTransactionalBoundary(Interaction interaction) {
+// log.debug("iaStarted");
+// kvStoreForTesting.incrementCounter(InteractionBoundaryProbe.class, "iaStarted");
+// }
+
+// /** INTERACTION END BOUNDARY */
+// @Override
+// public void afterLeavingTransactionalBoundary(Interaction interaction) {
+// log.debug("iaEnded");
+// kvStoreForTesting.incrementCounter(InteractionBoundaryProbe.class, "iaEnded");
+// }
+
+
+// /** TRANSACTION BEGIN BOUNDARY */
+// @EventListener(TransactionBeforeCompletionEvent.class) @Order(PriorityPrecedence.FIRST + 100)
+// public void onTransactionEnding(TransactionBeforeCompletionEvent event) {
- /** INTERACTION END BOUNDARY */
@Override
- public void afterLeavingTransactionalBoundary(Interaction interaction) {
- log.debug("iaEnded");
- kvStoreForTesting.incrementCounter(InteractionBoundaryProbe.class, "iaEnded");
- }
+ public void beforeCompletion() {
+ TransactionSynchronization.super.beforeCompletion();
- /** TRANSACTION BEGIN BOUNDARY */
- @EventListener(TransactionBeforeCompletionEvent.class) @Order(PriorityPrecedence.FIRST + 100)
- public void onTransactionEnding(TransactionBeforeCompletionEvent event) {
log.debug("txStarted");
kvStoreForTesting.incrementCounter(InteractionBoundaryProbe.class, "txEnding");
}
- /** TRANSACTION END BOUNDARY */
- @EventListener(TransactionAfterCompletionEvent.class) @Order(PriorityPrecedence.LAST - 100)
- public void onTransactionEnded(TransactionAfterCompletionEvent event) {
- if(event.isRolledBack()) {
+// /** TRANSACTION END BOUNDARY */
+// @EventListener(TransactionAfterCompletionEvent.class) @Order(PriorityPrecedence.LAST - 100)
+// public void onTransactionEnded(TransactionAfterCompletionEvent event) {
+
+ @Override
+ public void afterCompletion(int status) {
+ TransactionCompletionStatus transactionCompletionStatus = TransactionCompletionStatus.forStatus(status);
+
+ if(transactionCompletionStatus.isRolledBack()) {
kvStoreForTesting.incrementCounter(InteractionBoundaryProbe.class, "txRolledBack");
- } else if(event.isCommitted()) {
+ } else if(transactionCompletionStatus.isCommitted()) {
kvStoreForTesting.incrementCounter(InteractionBoundaryProbe.class, "txCommitted");
}
}
@@ -130,5 +148,4 @@ public class InteractionBoundaryProbe implements TransactionBoundaryAware {
return result;
}
-
}
(causeway) 06/06: CAUSEWAY-3654: refactors ScopedObjectsHolder to prevent stackoverflow...
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3654
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit b0d31d1305e2451564cfff9698a0a474ed0fcdb2
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Dec 13 06:55:01 2023 +0000
CAUSEWAY-3654: refactors ScopedObjectsHolder to prevent stackoverflow...
---
.../transaction/scope/StackedTransactionScope.java | 35 +++++++++++++++-------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
index 4bdbb94ccd..279fe879d7 100644
--- a/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
+++ b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
@@ -47,21 +47,17 @@ public class StackedTransactionScope implements Scope {
// but those scoped objects shouldn't only be interacted with during the transaction, not after it.
//
// see the 'else' clause for the handling if we encounter the ScopedObjectsHolder later.
- TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
+ registerWithTransitionSynchronizationManager(scopedObjects);
+ } else {
+ scopedObjects.registered = false;
}
TransactionSynchronizationManager.bindResource(key, scopedObjects);
} else {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// it's possible that this already-existing scopedObject was added when a synchronization wasn't active
- // and so wouldn't be known to TSM. if that's the case, we register it now.
- val synchronizations = TransactionSynchronizationManager.getSynchronizations();
- val synchronizedHolders = synchronizations.stream()
- .filter(CleanupSynchronization.class::isInstance)
- .map(CleanupSynchronization.class::cast)
- .map(x -> x.scopedObjects)
- .collect(Collectors.toList());
- if (! synchronizedHolders.contains(scopedObjects)) {
- TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
+ // and so wouldn't be registered to TSM. if that's the case, we register it now.
+ if (! scopedObjects.registered) {
+ registerWithTransitionSynchronizationManager(scopedObjects);
}
}
}
@@ -75,6 +71,11 @@ public class StackedTransactionScope implements Scope {
return scopedObject;
}
+ private void registerWithTransitionSynchronizationManager(ScopedObjectsHolder scopedObjects) {
+ TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
+ scopedObjects.registered = true;
+ }
+
@Override
@Nullable
public Object remove(String name) {
@@ -168,8 +169,20 @@ public class StackedTransactionScope implements Scope {
static class ScopedObjectsHolder {
final Map<String, Object> scopedInstances = new HashMap<>();
-
final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<>();
+
+ /**
+ * Keeps track of whether these objects have been registered with {@link TransactionSynchronizationManager}.
+ *
+ * <p>
+ * This can only be done if
+ * {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}, which
+ * isn't the case for {@link ScopedObjectsHolder scoped objects} that are obtained as a result of the
+ * {@link TransactionSynchronization#afterCompletion(int)} callback. We use this flag to keep track in
+ * case they are reused in a subsequent transaction.
+ * </p>
+ */
+ private boolean registered = false;
}
(causeway) 03/06: CAUSEWAY-3654: minor cleanup is all
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3654
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 07cb0a1e0dbcbaf2573ca870920323409e6df649
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Dec 12 11:58:25 2023 +0000
CAUSEWAY-3654: minor cleanup is all
---
.../session/InteractionServiceDefault.java | 21 ++++++------
.../transaction/TransactionServiceSpring.java | 38 +++++++++++-----------
2 files changed, 29 insertions(+), 30 deletions(-)
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 7ebe1c7f8e..c32deb1440 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,8 +30,6 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
-import org.apache.causeway.core.runtimeservices.transaction.TransactionServiceSpring;
-
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.event.ContextRefreshedEvent;
@@ -39,6 +37,7 @@ 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.services.clock.ClockService;
import org.apache.causeway.applib.services.command.Command;
import org.apache.causeway.applib.services.iactn.Interaction;
@@ -47,7 +46,6 @@ import org.apache.causeway.applib.services.iactnlayer.InteractionLayer;
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.services.xactn.TransactionService;
import org.apache.causeway.applib.util.schema.ChangesDtoUtils;
import org.apache.causeway.applib.util.schema.CommandDtoUtils;
import org.apache.causeway.applib.util.schema.InteractionDtoUtils;
@@ -66,6 +64,7 @@ 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 lombok.NonNull;
@@ -99,7 +98,6 @@ implements
final ClockService clockService;
final Provider<CommandPublisher> commandPublisherProvider;
- final Provider<TransactionService> transactionServiceProvider;
final ConfigurableBeanFactory beanFactory;
final InteractionScopeLifecycleHandler interactionScopeLifecycleHandler;
@@ -116,7 +114,6 @@ implements
final TransactionServiceSpring transactionServiceSpring,
final ClockService clockService,
final Provider<CommandPublisher> commandPublisherProvider,
- final Provider<TransactionService> transactionServiceProvider,
final ConfigurableBeanFactory beanFactory,
final InteractionIdGenerator interactionIdGenerator) {
this.runtimeEventService = runtimeEventService;
@@ -125,7 +122,6 @@ implements
this.transactionServiceSpring = transactionServiceSpring;
this.clockService = clockService;
this.commandPublisherProvider = commandPublisherProvider;
- this.transactionServiceProvider = transactionServiceProvider;
this.beanFactory = beanFactory;
this.interactionIdGenerator = interactionIdGenerator;
@@ -187,7 +183,7 @@ implements
val causewayInteraction = getOrCreateCausewayInteraction();
- // check whether we should reuse any current authenticationLayer,
+ // check whether we should reuse any current interactionLayer,
// that is, if current authentication and authToUse are equal
val reuseCurrentLayer = currentInteractionContext()
@@ -367,10 +363,9 @@ implements
Throwable flushException = null;
try {
- val txService = transactionServiceProvider.get();
- val mustAbort = txService.currentTransactionState().mustAbort();
+ val mustAbort = transactionServiceSpring.currentTransactionState().mustAbort();
if(!mustAbort) {
- txService.flushTransaction();
+ transactionServiceSpring.flushTransaction();
// publish only when flush was successful
completeAndPublishCurrentCommand();
}
@@ -427,7 +422,11 @@ implements
// -- HELPER - COMMAND COMPLETION
- private void completeAndPublishCurrentCommand() {
+ /**
+ * called by {@link TransactionServiceSpring}, but to be moved.
+ */
+ @Programmatic
+ public void completeAndPublishCurrentCommand() {
val interaction = getInternalInteractionElseFail();
val command = interaction.getCommand();
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
index e4fd820bad..5eb15b55c5 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/transaction/TransactionServiceSpring.java
@@ -30,6 +30,8 @@ import javax.inject.Provider;
import org.apache.causeway.applib.services.iactnlayer.InteractionContext;
+import org.apache.causeway.core.runtimeservices.session.InteractionServiceDefault;
+
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -365,7 +367,6 @@ implements
log.debug("opening on {}", _Probe.currentThreadId());
}
-
if (!platformTransactionManagers.isEmpty()) {
val onCloseTasks = _Lists.<CloseTask>newArrayList(platformTransactionManagers.size());
@@ -388,26 +389,25 @@ implements
// we have created a new transaction, so need to provide a CloseTask
onCloseTasks.add(
- new CloseTask(
- txStatus,
- txManager.getClass().getName(), // info to be used for display in case of errors
- () -> {
- _Xray.txBeforeCompletion(interactionLayerTrackerProvider.get(), "tx: beforeCompletion");
- final TransactionCompletionStatus event;
- if (txStatus.isRollbackOnly()) {
- txManager.rollback(txStatus);
- event = TransactionCompletionStatus.ROLLED_BACK;
- } else {
- txManager.commit(txStatus);
- event = TransactionCompletionStatus.COMMITTED;
- }
- _Xray.txAfterCompletion(interactionLayerTrackerProvider.get(), String.format("tx: afterCompletion (%s)", event.name()));
-
- txCounter.get().increment();
+ new CloseTask(
+ txStatus,
+ txManager.getClass().getName(), // info to be used for display in case of errors
+ () -> {
+ _Xray.txBeforeCompletion(interactionLayerTrackerProvider.get(), "tx: beforeCompletion");
+ final TransactionCompletionStatus event;
+ if (txStatus.isRollbackOnly()) {
+ txManager.rollback(txStatus);
+ event = TransactionCompletionStatus.ROLLED_BACK;
+ } else {
+ txManager.commit(txStatus);
+ event = TransactionCompletionStatus.COMMITTED;
}
- )
- );
+ _Xray.txAfterCompletion(interactionLayerTrackerProvider.get(), String.format("tx: afterCompletion (%s)", event.name()));
+ txCounter.get().increment();
+ }
+ )
+ );
});
}
}
(causeway) 05/06: CAUSEWAY-3654: adds guards to handle afterCompletion call
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3654
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit d7551153aac85a731b137323636135e31edec23f
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Dec 12 17:58:00 2023 +0000
CAUSEWAY-3654: adds guards to handle afterCompletion call
---
.../transaction/scope/StackedTransactionScope.java | 29 ++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
index d1a431fb9e..4bdbb94ccd 100644
--- a/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
+++ b/core/transaction/src/main/java/org/apache/causeway/core/transaction/scope/StackedTransactionScope.java
@@ -15,10 +15,14 @@
*/
package org.apache.causeway.core.transaction.scope;
+import lombok.val;
+
import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Stack;
+import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
@@ -37,9 +41,30 @@ public class StackedTransactionScope implements Scope {
ScopedObjectsHolder scopedObjects = (ScopedObjectsHolder) TransactionSynchronizationManager.getResource(key);
if (scopedObjects == null) {
scopedObjects = new ScopedObjectsHolder();
- TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ // this happen when TransactionSynchronization#afterCompletion is called.
+ // it's a catch-22 : we use TransactionSynchronization as a resource to hold the scoped objects,
+ // but those scoped objects shouldn't only be interacted with during the transaction, not after it.
+ //
+ // see the 'else' clause for the handling if we encounter the ScopedObjectsHolder later.
+ TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
+ }
TransactionSynchronizationManager.bindResource(key, scopedObjects);
- }
+ } else {
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ // it's possible that this already-existing scopedObject was added when a synchronization wasn't active
+ // and so wouldn't be known to TSM. if that's the case, we register it now.
+ val synchronizations = TransactionSynchronizationManager.getSynchronizations();
+ val synchronizedHolders = synchronizations.stream()
+ .filter(CleanupSynchronization.class::isInstance)
+ .map(CleanupSynchronization.class::cast)
+ .map(x -> x.scopedObjects)
+ .collect(Collectors.toList());
+ if (! synchronizedHolders.contains(scopedObjects)) {
+ TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
+ }
+ }
+ }
// NOTE: Do NOT modify the following to use Map::computeIfAbsent. For details,
// see https://github.com/spring-projects/spring-framework/issues/25801.
Object scopedObject = scopedObjects.scopedInstances.get(name);