You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2019/06/22 08:07:44 UTC

[isis] branch v2 updated: ISIS-2125: replaces the TransactionServiceDefault with a new TransactionServiceSpring that utilizes sprint-tx API

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch v2
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/v2 by this push:
     new 4efd9b0  ISIS-2125: replaces the TransactionServiceDefault with a new TransactionServiceSpring that utilizes sprint-tx API
4efd9b0 is described below

commit 4efd9b07d0dfe167614fbdbe90dbb95e0b2f258c
Author: Andi Huber <ah...@apache.org>
AuthorDate: Sat Jun 22 10:07:33 2019 +0200

    ISIS-2125: replaces the TransactionServiceDefault with a new
    TransactionServiceSpring that utilizes sprint-tx API
    
    - however, the nextTransaction() call is still delegated to the old
    service, unfortunately this concept is not present in Spring's tx API
    - remove the transaction latch from ps-internal
    - deal with the fact that IsisSession.close(), as handled by Spring when
    processing @PreDestroy, runs on a different thread than that, that
    opened the session.
    
    Task-Url: https://issues.apache.org/jira/browse/ISIS-2125
---
 .../isis/applib/services/xactn/Transaction.java    | 21 ++++++
 .../isis/applib/services/xactn/TransactionId.java  |  6 ++
 .../applib/services/xactn/TransactionService.java  |  1 -
 .../commons/internal/collections/_Multimaps.java   |  8 +++
 .../isis/commons/internal/context/_Context.java    |  6 +-
 .../internal/context/_Context_ThreadLocal.java     | 81 +++++++++++++++++++---
 .../PersistenceSessionServiceInternal.java         |  5 +-
 .../IsisPlatformTransactionManagerForJdo.java      | 27 ++++++--
 ...eDefault.java => TransactionServiceLegacy.java} | 42 +++++------
 .../services/xactn/TransactionServiceSpring.java   | 39 +++++++----
 .../context/session/RuntimeEventService.java       |  5 +-
 .../JdoPersistenceLifecycleService.java            |  1 +
 .../core/runtime/system/session/IsisSession.java   | 17 ++++-
 .../system/session/IsisSessionFactoryDefault.java  | 14 +++-
 .../system/transaction/IsisTransactionObject.java  |  6 +-
 .../PersistenceSessionServiceInternalDefault.java  | 15 ++--
 .../server/resources/DomainResourceHelper.java     |  2 +
 .../isis/testdomain/tests/AuditerServiceTest.java  |  9 +--
 .../testdomain/tests/JdoBootstrappingTest.java     | 31 +++++++--
 .../tests/SpringServiceProvisioningTest.java       |  2 +
 .../isis/testdomain/tests/builtin-IsisBoot.json    |  1 -
 21 files changed, 248 insertions(+), 91 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/xactn/Transaction.java b/core/applib/src/main/java/org/apache/isis/applib/services/xactn/Transaction.java
index 886fae5..1c69df0 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/xactn/Transaction.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/xactn/Transaction.java
@@ -46,5 +46,26 @@ public interface Transaction {
 
     @Programmatic
     TransactionState getTransactionState();
+    
+    // -- FACTORIES
+    
+    public static Transaction noop() {
+    	return new Transaction() {
+			
+			@Override
+			public TransactionState getTransactionState() {
+				return TransactionState.NONE;
+			}
+			
+			@Override
+			public TransactionId getId() {
+				return null;
+			}
+			
+			@Override
+			public void flush() {
+			}
+		};
+    }
 
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionId.java b/core/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionId.java
index 25216c0..007349e 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionId.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionId.java
@@ -18,4 +18,10 @@ public final class TransactionId implements HasUniqueId {
      */
 	private final int sequence;
 
+	private final static TransactionId EMPTY = TransactionId.of(UUID.fromString("0000-00-00-00-000000"), 0); 
+	
+	public static TransactionId empty() {
+		return EMPTY;
+	}
+
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionService.java b/core/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionService.java
index f698998..77db6d1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/xactn/TransactionService.java
@@ -72,7 +72,6 @@ public interface TransactionService {
     @Programmatic
     CountDownLatch currentTransactionLatch();
 
-
     /**
      * Intended only for use by fixture scripts and integration tests.
      *
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java b/core/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
index 7d4f291..bad67c2 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
@@ -28,6 +28,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
 
 /**
@@ -224,6 +225,13 @@ public class _Multimaps {
     public static <K1, K2, V> MapMultimap<K1, K2, V> newMapMultimap(){
         return newMapMultimap(HashMap<K1, Map<K2, V>>::new, HashMap::new);
     }
+    
+    /**
+     * @return ConcurrentHashMap of HashMaps
+     */
+    public static <K1, K2, V> MapMultimap<K1, K2, V> newConcurrentMapMultimap(){
+        return newMapMultimap(ConcurrentHashMap<K1, Map<K2, V>>::new, HashMap::new);
+    }
 
 
 
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context.java b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context.java
index 118e084..1edfdde 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context.java
@@ -243,6 +243,7 @@ public final class _Context {
             closeAnyClosables(_Lists.newArrayList(singletonMap.values()));
 
             singletonMap.clear();
+            _Context_ThreadLocal.clear();
         }
     }
 
@@ -265,9 +266,10 @@ public final class _Context {
      * Puts {@code payload} onto the current thread's map.
      * @param type - the key into the thread-local store
      * @param payload
+     * @return a Runnable which, when run, removes any references to payload
      */
-	public static <T> void threadLocalPut(Class<? super T> type, T payload) {
-		_Context_ThreadLocal.put(type, payload);
+	public static <T> Runnable threadLocalPut(Class<? super T> type, T payload) {
+		return _Context_ThreadLocal.put(type, payload);
 	}
 
     
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal.java b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal.java
index aa98fdd..eeae901 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/context/_Context_ThreadLocal.java
@@ -21,12 +21,14 @@ package org.apache.isis.commons.internal.context;
 
 import static org.apache.isis.commons.internal.base._With.requires;
 
-import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.commons.internal.collections._Maps;
+import org.apache.isis.commons.internal.collections._Multimaps;
 import org.apache.isis.core.commons.collections.Bin;
 
+import lombok.Value;
 import lombok.val;
 
 /**
@@ -39,19 +41,36 @@ import lombok.val;
  * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
  * These may be changed or removed without notice!
  * </p>
+ * 
  * @since 2.0.0-M3
+ * 
  */
 final class _Context_ThreadLocal {
 
     // -- MIXINS
     
-	static <T> void put(Class<? super T> type, T variant) {
+	@Value(staticConstructor = "of")
+	private final static class ThreadKey {
+		long threadId;
+		int threadHashCode;
+		static ThreadKey of(Thread thread) {
+			return of(thread.getId(), thread.hashCode());
+		}
+	}
+	
+	static <T> Runnable put(Class<? super T> type, T variant) {
 		requires(type, "type");
     	requires(variant, "variant");
-    	THREAD_LOCAL_MAP.get()
+    	
+    	val threadLocalMap = getOrCreateThreadLocalMap();
+    	threadLocalMap
     	.compute(type, (k, v) -> v == null 
     		? Bin.<T>ofSingleton(variant)
     				: Bin.<T>concat(_Casts.uncheckedCast(v), variant));
+    	
+    	val key = THREAD_LOCAL_MAP_KEY.get();
+    	    	
+		return ()->{MAPS_BY_KEY.remove(key);};
     }
 	
 	static <T> Bin<T> select(Class<? super T> type, Class<? super T> instanceOf) {
@@ -64,30 +83,70 @@ final class _Context_ThreadLocal {
 	}
     
     static <T> Bin<T> get(Class<? super T> type) {
-    	val bin = THREAD_LOCAL_MAP.get().get(type);
-    	if(bin!=null) {
-    		return _Casts.uncheckedCast(bin);
+    	val threadLocalMap = getThreadLocalMap();
+    	if(threadLocalMap==null) {
+    		return Bin.empty();
     	}
-    	return Bin.empty();
+    	val bin = threadLocalMap.get(type);
+    	if(bin==null) {
+    		return Bin.empty();	
+    	}
+    	return _Casts.uncheckedCast(bin);
     }
     
     static void clear(Class<?> type) {
-    	THREAD_LOCAL_MAP.get().remove(type);
+    	val threadLocalMap = getThreadLocalMap();
+    	if(threadLocalMap==null) {
+    		return;
+    	}
+    	threadLocalMap.remove(type);
     }
     
     static void cleanupThread() {
-    	THREAD_LOCAL_MAP.remove();
+    	val key = THREAD_LOCAL_MAP_KEY.get();
+    	THREAD_LOCAL_MAP_KEY.remove();
+    	MAPS_BY_KEY.remove(key);
     }
     
     // -- HELPER
     
     private _Context_ThreadLocal(){}
+    
+    static void clear() {
+    	MAPS_BY_KEY.clear();
+    }
+
+//	/**
+//	 * Inheritable... allows to have concurrent computations utilizing the ForkJoinPool.
+//	 */
+//    private final static ThreadLocal<Map<Class<?>, Bin<?>>> THREAD_LOCAL_MAP = 
+//    		InheritableThreadLocal.withInitial(HashMap::new);
 
 	/**
 	 * Inheritable... allows to have concurrent computations utilizing the ForkJoinPool.
 	 */
-    private final static ThreadLocal<Map<Class<?>, Bin<?>>> THREAD_LOCAL_MAP = 
-    		InheritableThreadLocal.withInitial(HashMap::new);
+    private final static ThreadLocal<ThreadKey> THREAD_LOCAL_MAP_KEY = 
+    		InheritableThreadLocal.withInitial(()->ThreadKey.of(Thread.currentThread()));
+
+    
+    private final static _Multimaps.MapMultimap<ThreadKey, Class<?>, Bin<?>> MAPS_BY_KEY = 
+    		_Multimaps.newConcurrentMapMultimap(); 
+    
+    private static Map<Class<?>, Bin<?>> getThreadLocalMap() {
+    	val key = THREAD_LOCAL_MAP_KEY.get(); // non-null
+    	val threadLocalMap = MAPS_BY_KEY.get(key); // might be null
+    	return threadLocalMap;
+    }
     
+    private static Map<Class<?>, Bin<?>> getOrCreateThreadLocalMap() {
+    	val key = THREAD_LOCAL_MAP_KEY.get(); // non-null
+    	val threadLocalMap = MAPS_BY_KEY.get(key); // might be null
+    	if(threadLocalMap!=null) {
+        	return threadLocalMap;
+    	}
+		val map = _Maps.<Class<?>, Bin<?>>newHashMap();
+		MAPS_BY_KEY.put(key, map);
+		return map;
+    }
     
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/persistsession/PersistenceSessionServiceInternal.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/persistsession/PersistenceSessionServiceInternal.java
index 1f60330..c6c150a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/persistsession/PersistenceSessionServiceInternal.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/persistsession/PersistenceSessionServiceInternal.java
@@ -17,7 +17,6 @@
 package org.apache.isis.core.metamodel.services.persistsession;
 
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
 import java.util.function.Supplier;
 
 import org.apache.isis.applib.query.Query;
@@ -93,8 +92,6 @@ public interface PersistenceSessionServiceInternal extends ObjectAdapterProvider
 
     Transaction currentTransaction();
 
-    CountDownLatch currentTransactionLatch();
-
     TransactionState getTransactionState();
 
     // -- makePersistent, remove
@@ -135,8 +132,10 @@ public interface PersistenceSessionServiceInternal extends ObjectAdapterProvider
      */
     <T> ObjectAdapter firstMatchingQuery(Query<T> query);
 
+    @Deprecated //TODO[2125] use new TransactionServiceSpring instead
     void executeWithinTransaction(Runnable task);
     
+    @Deprecated //TODO[2125] use new TransactionServiceSpring instead
     <T> T executeWithinTransaction(Supplier<T> task);
 
 }
diff --git a/core/plugins/jdo-common/src/main/java/org/apache/isis/jdo/transaction/IsisPlatformTransactionManagerForJdo.java b/core/plugins/jdo-common/src/main/java/org/apache/isis/jdo/transaction/IsisPlatformTransactionManagerForJdo.java
index 9862cf3..0272c77 100644
--- a/core/plugins/jdo-common/src/main/java/org/apache/isis/jdo/transaction/IsisPlatformTransactionManagerForJdo.java
+++ b/core/plugins/jdo-common/src/main/java/org/apache/isis/jdo/transaction/IsisPlatformTransactionManagerForJdo.java
@@ -21,12 +21,15 @@ package org.apache.isis.jdo.transaction;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import org.apache.isis.applib.services.registry.ServiceRegistry;
+import org.apache.isis.applib.services.xactn.Transaction;
 import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
 import org.apache.isis.core.runtime.system.internal.InitialisationSession;
 import org.apache.isis.core.runtime.system.session.IsisSession;
 import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionAspectSupport;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionObject;
+import org.apache.isis.core.security.authentication.AuthenticationSession;
 import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionException;
 import org.springframework.transaction.support.AbstractPlatformTransactionManager;
@@ -42,17 +45,29 @@ public class IsisPlatformTransactionManagerForJdo extends AbstractPlatformTransa
 	
 	@Inject private IsisSessionFactory isisSessionFactory;
 	@Inject private PersistenceSessionServiceInternal persistenceSessionServiceInternal;
+	@Inject private ServiceRegistry serviceRegistry;
 
 	@Override
 	protected Object doGetTransaction() throws TransactionException {
-		log.debug("doGetTransaction");
 		
-		if(IsisSession.isInSession()) {
-			val transactionBeforeBegin = persistenceSessionServiceInternal.currentTransaction(); 
-			return IsisTransactionObject.of(transactionBeforeBegin);	
+		val isInSession = IsisSession.isInSession();
+		log.debug("doGetTransaction isInSession={}", isInSession);
+		
+		if(!isInSession) {
+
+			// get authenticationSession from IoC, or fallback to InitialisationSession 
+			val authenticationSession = serviceRegistry.select(AuthenticationSession.class)
+					.getFirst()
+					.orElseGet(InitialisationSession::new);
+					
+			log.debug("open new session authenticationSession={}", authenticationSession);
+			
+			isisSessionFactory.openSession(authenticationSession);
+			val transactionBeforeBegin = (Transaction)null;
+			return IsisTransactionObject.of(transactionBeforeBegin);
 		} else {
-			isisSessionFactory.openSession(new InitialisationSession()); //FIXME this is only for integration testing
-			return IsisTransactionObject.of(null);
+			val transactionBeforeBegin = persistenceSessionServiceInternal.currentTransaction(); 
+			return IsisTransactionObject.of(transactionBeforeBegin);
 		}
 		
 	}
diff --git a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceDefault.java b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceLegacy.java
similarity index 79%
rename from core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceDefault.java
rename to core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceLegacy.java
index 1d259bb..3c845c0 100644
--- a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceDefault.java
+++ b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceLegacy.java
@@ -19,11 +19,8 @@
 
 package org.apache.isis.core.runtime.services.xactn;
 
-import java.util.concurrent.CountDownLatch;
 import java.util.function.Supplier;
 
-import javax.inject.Singleton;
-
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.xactn.Transaction;
 import org.apache.isis.applib.services.xactn.TransactionId;
@@ -35,19 +32,19 @@ import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManagerJdoInternal;
 
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 
-@Singleton
-public class TransactionServiceDefault implements TransactionService {
-
+@RequiredArgsConstructor(staticName = "of")
+class TransactionServiceLegacy {
 
-    @Override
-    public void flushTransaction() {
+    void flushTransaction() {
         persistenceSessionServiceInternal.flush();
     }
 
-    @Override
-    public void nextTransaction(TransactionService.Policy policy, final Command commandIfAny) {
+    
+    void nextTransaction(TransactionService.Policy policy, final Command commandIfAny) {
         final TransactionState transactionState = currentTransactionState();
         switch (transactionState) {
         case NONE:
@@ -77,13 +74,13 @@ public class TransactionServiceDefault implements TransactionService {
         persistenceSessionServiceInternal.beginTran(commandIfAny);
     }
 
-	@Override
-    public void executeWithinTransaction(Runnable task) {
+	
+    void executeWithinTransaction(Runnable task) {
         isisTransactionManager().executeWithinTransaction(task);
     }
 
-    @Override
-    public <T> T executeWithinTransaction(Supplier<T> task) {
+    
+    <T> T executeWithinTransaction(Supplier<T> task) {
         return isisTransactionManager().executeWithinTransaction(task);
     }
     
@@ -91,19 +88,14 @@ public class TransactionServiceDefault implements TransactionService {
         return IsisContext.getTransactionManagerJdo().get();
     }
     
-    @Override
-    public TransactionId currentTransactionId() {
+    
+    TransactionId currentTransactionId() {
     	val tx = currentTransaction();
     	return tx!=null ? tx.getId() : null;
     }
 
-    @Override
-    public CountDownLatch currentTransactionLatch() {
-        return persistenceSessionServiceInternal.currentTransactionLatch();
-    }
-
-    @Override
-    public TransactionState currentTransactionState() {
+    
+    TransactionState currentTransactionState() {
         return persistenceSessionServiceInternal.getTransactionState();
     }
     
@@ -113,8 +105,8 @@ public class TransactionServiceDefault implements TransactionService {
 		return persistenceSessionServiceInternal.currentTransaction();
 	}
 
-    @javax.inject.Inject
-    PersistenceSessionServiceInternal persistenceSessionServiceInternal;
+    @NonNull
+    private final PersistenceSessionServiceInternal persistenceSessionServiceInternal;
 
 
 }
diff --git a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceSpring.java b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceSpring.java
index 4ad6c9e..814f0d6 100644
--- a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceSpring.java
+++ b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/xactn/TransactionServiceSpring.java
@@ -22,7 +22,8 @@ package org.apache.isis.core.runtime.services.xactn;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Supplier;
 
-import javax.enterprise.inject.Vetoed;
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
 import javax.inject.Singleton;
 
 import org.apache.isis.applib.services.command.Command;
@@ -30,6 +31,8 @@ import org.apache.isis.applib.services.xactn.TransactionId;
 import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.applib.services.xactn.TransactionState;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
+import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
+import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionAspectSupport;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionObject;
 import org.springframework.transaction.PlatformTransactionManager;
@@ -41,16 +44,25 @@ import org.springframework.transaction.support.TransactionTemplate;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
-@Vetoed @Deprecated//does not work
+@Singleton
 @Log4j2
 public class TransactionServiceSpring implements TransactionService {
 	
+	@Inject private PersistenceSessionServiceInternal persistenceSessionServiceInternal;
+	
 	 // single TransactionTemplate shared amongst all methods in this instance
     private final TransactionTemplate transactionTemplate;
+    private TransactionServiceLegacy legacyTransactionService;
 
     // use constructor-injection to supply the PlatformTransactionManager
     public TransactionServiceSpring(PlatformTransactionManager transactionManager) {
         this.transactionTemplate = new TransactionTemplate(transactionManager);
+        
+    }
+    
+    @PostConstruct
+    public void init() {
+    	this.legacyTransactionService = TransactionServiceLegacy.of(persistenceSessionServiceInternal);
     }
 
 	@Override
@@ -108,22 +120,23 @@ public class TransactionServiceSpring implements TransactionService {
 	}
 
 	@Override
-	public CountDownLatch currentTransactionLatch() {
+	public void nextTransaction(Policy policy, Command command) {
 		
-		log.warn("not implemented");
-		_Exceptions.throwNotImplemented();
+		log.warn("deprecated");
+		//_Exceptions.throwNotImplemented();
 		
-		// TODO Auto-generated method stub
-		return null;
+		legacyTransactionService.nextTransaction(policy, command);
 	}
-
+	
 	@Override
-	public void nextTransaction(Policy policy, Command command) {
-		// TODO Auto-generated method stub
+	public CountDownLatch currentTransactionLatch() {
 		
-		log.warn("not implemented");
-		_Exceptions.throwNotImplemented();
+		val txObject = IsisTransactionAspectSupport.currentTransactionObject();
+		if(txObject==null || txObject.getCurrentTransaction()==null) {
+			return new CountDownLatch(0);
+		}
 		
+        return ((IsisTransaction)txObject.getCurrentTransaction()).countDownLatch();
 	}
 
 	@Override
@@ -163,4 +176,6 @@ public class TransactionServiceSpring implements TransactionService {
 	}
 
 
+
+
 }
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/session/RuntimeEventService.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/session/RuntimeEventService.java
index 1b680e9..2233c37 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/session/RuntimeEventService.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/session/RuntimeEventService.java
@@ -30,7 +30,8 @@ import org.apache.isis.core.runtime.system.session.IsisSession;
 /**
  * 
  * @since 2.0.0-M3
- *
+ * @implNote listeners to runtime events are hard-wired, because these events are already fired 
+ * during bootstrapping, when event handling might not work properly yet.   
  */
 @Singleton
 public class RuntimeEventService {
@@ -44,8 +45,6 @@ public class RuntimeEventService {
 	
 	// -- APP
 
-    //FIXME[2112] events are fired but not received
-    
 	public void fireAppPreMetamodel() {
 		//appLifecycleEvents.fire(AppLifecycleEvent.of(AppLifecycleEvent.EventType.appPreMetamodel));
 		listener.onAppLifecycleEvent(AppLifecycleEvent.of(AppLifecycleEvent.EventType.appPreMetamodel));
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/JdoPersistenceLifecycleService.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/JdoPersistenceLifecycleService.java
index f8a89e3..dab9bc1 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/JdoPersistenceLifecycleService.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/JdoPersistenceLifecycleService.java
@@ -119,6 +119,7 @@ public class JdoPersistenceLifecycleService {
 		PersistenceSession.current(PersistenceSession.class)
                 .getSingleton()
                 .ifPresent(PersistenceSession::close);
+		_Context.threadLocalClear(PersistenceSession.class);
 	}
 	
 //	private void flushSession() {
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java
index 2011fbf..cad9388 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java
@@ -51,7 +51,14 @@ public class IsisSession extends RuntimeContextBase {
      */
 	@Getter private long openedAtSystemNanos = -1L;
     
-	public IsisSession(
+	/**
+	 * 
+	 * @param runtimeEventService
+	 * @param authenticationSession
+	 * @implNote package private constructor, to let only IsisSessionFactory have access to it, 
+	 * since it must keep track of all opened sessions
+	 */
+	IsisSession(
 			final RuntimeEventService runtimeEventService,
 			final AuthenticationSession authenticationSession) {
 		
@@ -99,10 +106,12 @@ public class IsisSession extends RuntimeContextBase {
 	}
 	
 	// -- OPEN
+	
+	private Runnable cleanupHandle;  
     
     void open() {
     	openedAtSystemNanos = System.nanoTime();
-    	_Context.threadLocalPut(IsisSession.class, this);
+    	cleanupHandle = _Context.threadLocalPut(IsisSession.class, this);
     	runtimeEventService.fireSessionOpened(this);
     }
 
@@ -113,7 +122,9 @@ public class IsisSession extends RuntimeContextBase {
      */
     void close() {
         runtimeEventService.fireSessionClosing(this);
-        _Context.threadLocalCleanup();
+        if(cleanupHandle!=null) {
+        	cleanupHandle.run();
+        }
     }
 
     // -- FLUSH
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionFactoryDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionFactoryDefault.java
index 438f96e..e831874 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionFactoryDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionFactoryDefault.java
@@ -24,6 +24,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Future;
 import java.util.stream.Stream;
@@ -39,6 +40,7 @@ import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.applib.services.registry.ServiceRegistry;
 import org.apache.isis.applib.services.title.TitleService;
 import org.apache.isis.commons.internal.base._Blackhole;
+import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.commons.internal.context._Context;
 import org.apache.isis.commons.ioc.BeanAdapter;
 import org.apache.isis.config.IsisConfiguration;
@@ -56,8 +58,8 @@ import org.apache.isis.core.runtime.system.context.session.RuntimeEventService;
 import org.apache.isis.core.runtime.system.internal.InitialisationSession;
 import org.apache.isis.core.runtime.system.internal.IsisLocaleInitializer;
 import org.apache.isis.core.runtime.system.internal.IsisTimeZoneInitializer;
-import org.apache.isis.core.runtime.system.transaction.IsisTransactionManagerJdoInternal;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManagerException;
+import org.apache.isis.core.runtime.system.transaction.IsisTransactionManagerJdoInternal;
 import org.apache.isis.core.runtime.threadpool.ThreadPoolSupport;
 import org.apache.isis.core.security.authentication.AuthenticationSession;
 import org.apache.isis.core.security.authentication.manager.AuthenticationManager;
@@ -257,12 +259,15 @@ public class IsisSessionFactoryDefault implements IsisSessionFactory {
     @PreDestroy
     @Override
     public void destroyServicesAndShutdown() {
+    	// call might originate from a different thread than main
         destroyServices();
         shutdown();
     }
 
     // -- 
     
+    private final Set<IsisSession> openSessions = _Sets.newHashSet();
+    
     @Override
     public IsisSession openSession(final AuthenticationSession authenticationSession) {
 
@@ -270,6 +275,7 @@ public class IsisSessionFactoryDefault implements IsisSessionFactory {
 
         val isisSession = new IsisSession(runtimeEventService, authenticationSession);
         isisSession.open();
+        openSessions.add(isisSession);
         return isisSession;
     }
 
@@ -281,6 +287,7 @@ public class IsisSessionFactoryDefault implements IsisSessionFactory {
             return;
         }
         existingSessionIfAny.close();
+        openSessions.remove(existingSessionIfAny);
     }
 
     @Override
@@ -367,6 +374,11 @@ public class IsisSessionFactoryDefault implements IsisSessionFactory {
     }
 
     private void shutdown() {
+    	
+    	// just in case we still have an open session, must also work if called from a different thread than 'main'
+    	openSessions.forEach(IsisSession::close);
+    	openSessions.clear();
+		
         runtimeEventService.fireAppPreDestroy();
         authenticationManager.shutdown();
         //specificationLoader.shutdown(); //[2112] lifecycle is managed by IoC
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionObject.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionObject.java
index 925a776..c3ca9ed 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionObject.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/IsisTransactionObject.java
@@ -27,8 +27,9 @@ import lombok.Data;
 
 @Data @AllArgsConstructor(staticName = "of")
 public class IsisTransactionObject implements SmartTransactionObject {
+	
 	Transaction currentTransaction;
-
+	
 	@Override
 	public boolean isRollbackOnly() {
 		return currentTransaction.getTransactionState().mustAbort();
@@ -43,8 +44,9 @@ public class IsisTransactionObject implements SmartTransactionObject {
 	
 	public TransactionId getTransactionId() {
 		if(currentTransaction!=null) {
-			currentTransaction.getId();
+			return currentTransaction.getId();
 		}
 		return null;
 	}		
+
 }
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/PersistenceSessionServiceInternalDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/PersistenceSessionServiceInternalDefault.java
index 02b2f30..88dcaa9 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/PersistenceSessionServiceInternalDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/transaction/PersistenceSessionServiceInternalDefault.java
@@ -18,8 +18,11 @@
  */
 package org.apache.isis.core.runtime.system.transaction;
 
+import static java.util.Objects.requireNonNull;
+import static org.apache.isis.commons.internal.base._With.acceptIfPresent;
+import static org.apache.isis.commons.internal.base._With.mapIfPresentElse;
+
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
 import java.util.function.Supplier;
 
 import javax.inject.Singleton;
@@ -45,10 +48,6 @@ import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
 import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
 
-import static java.util.Objects.requireNonNull;
-import static org.apache.isis.commons.internal.base._With.acceptIfPresent;
-import static org.apache.isis.commons.internal.base._With.mapIfPresentElse;
-
 @Singleton
 public class PersistenceSessionServiceInternalDefault 
 implements PersistenceSessionServiceInternal {
@@ -167,12 +166,6 @@ implements PersistenceSessionServiceInternal {
     }
 
     @Override
-    public CountDownLatch currentTransactionLatch() {
-        IsisTransaction transaction = getTransactionManager().getCurrentTransaction();
-        return transaction==null ? new CountDownLatch(0) : transaction.countDownLatch();
-    }
-
-    @Override
     public <T> List<ObjectAdapter> allMatchingQuery(final Query<T> query) {
         return getPersistenceSession().allMatchingQuery(query);
     }
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
index 75bac90..64c3abc 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/DomainResourceHelper.java
@@ -40,6 +40,8 @@ import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.runtime.memento.Data;
 import org.apache.isis.core.runtime.persistence.FixturesInstalledState;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.transaction.IsisTransactionAspectSupport;
 import org.apache.isis.core.security.authentication.AuthenticationSession;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
diff --git a/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/AuditerServiceTest.java b/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/AuditerServiceTest.java
index c7bd309..225b0f9 100644
--- a/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/AuditerServiceTest.java
+++ b/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/AuditerServiceTest.java
@@ -58,6 +58,7 @@ import lombok.extern.log4j.Log4j2;
 		properties = {
 				"logging.config=log4j2-test.xml",
 				"logging.level.org.apache.isis.jdo.transaction.IsisPlatformTransactionManagerForJdo=DEBUG",
+				"logging.level.org.apache.isis.core.runtime.system.transaction.IsisTransaction=DEBUG",
 				// "isis.reflector.introspector.parallelize=false",
 				// "logging.level.org.apache.isis.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract=TRACE"
 	})
@@ -134,17 +135,17 @@ class AuditerServiceTest {
 					.append(propertyName).append(",").append("preValue=").append(preValue).append(",")
 					.append("postValue=").append(postValue).append(";");
 			
-			log.info("audit {}", history.toString());
+			log.debug("audit {}", history.toString());
 		}
 
 		void clearHistory() {
-			log.info("clearing");
+			log.debug("clearing");
 			history = new StringBuilder();
-			log.info("cleared");
+			log.debug("cleared");
 		}
 
 		String getHistory() {
-			log.info("get");
+			log.debug("get {}", history.toString());
 			return history.toString();
 		}
 
diff --git a/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/JdoBootstrappingTest.java b/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/JdoBootstrappingTest.java
index 9aee72d..a21e8f4 100644
--- a/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/JdoBootstrappingTest.java
+++ b/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/JdoBootstrappingTest.java
@@ -20,6 +20,7 @@ package org.apache.isis.testdomain.tests;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 
 import java.sql.SQLException;
 import java.util.HashSet;
@@ -28,14 +29,19 @@ import java.util.Set;
 import javax.inject.Inject;
 
 import org.apache.isis.applib.services.repository.RepositoryService;
+import org.apache.isis.core.runtime.system.session.IsisSession;
 import org.apache.isis.jdo.transaction.IsisPlatformTransactionManagerForJdo;
 import org.apache.isis.runtime.spring.IsisBoot;
 import org.apache.isis.testdomain.jdo.Book;
 import org.apache.isis.testdomain.jdo.Inventory;
 import org.apache.isis.testdomain.jdo.JdoTestDomainModule;
 import org.apache.isis.testdomain.jdo.Product;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.transaction.annotation.Transactional;
@@ -54,16 +60,24 @@ import lombok.val;
 		// "isis.reflector.introspector.parallelize=false",
 		 //"logging.level.org.apache.isis.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract=TRACE"
 })
-@Transactional
+@Transactional @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 class JdoBootstrappingTest {
 
 	@Inject private RepositoryService repository;
 
 	@BeforeAll
-	static void launchH2Console() throws SQLException {
-		// for troubleshooting ...
+	static void beforeAll() throws SQLException {
+		//XXX is it actually the case that tests might run in parallel?
+		//assertFalse(IsisSession.isInSession()); // expected pre condition 
+		// launch H2Console for troubleshooting ...
 		// Util_H2Console.main(null);
 	}
+	
+	@AfterAll
+	static void afterAll() throws SQLException {
+//		assertFalse(IsisSession.isInSession()); // expected post condition
+	}
+	
 
 	void cleanUp() {
 
@@ -85,9 +99,9 @@ class JdoBootstrappingTest {
 		repository.persist(inventory);
 	}
 
-	@Test @Rollback(false)
+	@Test @Order(1) @Rollback(false) 
 	void sampleInventoryShouldBeSetUp() {
-
+		
 		// given - expected pre condition: no inventories
 
 		cleanUp();
@@ -99,7 +113,7 @@ class JdoBootstrappingTest {
 
 		// then - expected post condition: ONE inventory
 
-		val inventories =repository.allInstances(Inventory.class);
+		val inventories = repository.allInstances(Inventory.class);
 		assertEquals(1, inventories.size());
 
 		val inventory = inventories.get(0);
@@ -111,5 +125,10 @@ class JdoBootstrappingTest {
 		assertEquals("Sample Book", product.getName());
 
 	}
+	
+	@Test @Order(2) @Rollback(false)
+	void aSecondRunShouldWorkAsWell() {
+		sampleInventoryShouldBeSetUp();
+	}
 
 }
diff --git a/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/SpringServiceProvisioningTest.java b/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/SpringServiceProvisioningTest.java
index 202f891..0e4018e 100644
--- a/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/SpringServiceProvisioningTest.java
+++ b/example/misc/smoketest/src/test/java/org/apache/isis/testdomain/tests/SpringServiceProvisioningTest.java
@@ -31,6 +31,7 @@ import org.apache.isis.commons.internal.resources._Json;
 import org.apache.isis.commons.internal.resources._Resources;
 import org.apache.isis.commons.ioc.BeanAdapter;
 import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.jdo.transaction.IsisPlatformTransactionManagerForJdo;
 import org.apache.isis.runtime.spring.IsisBoot;
 import org.apache.isis.testdomain.jdo.JdoTestDomainModule;
 import org.junit.jupiter.api.BeforeEach;
@@ -43,6 +44,7 @@ import lombok.val;
 	classes = { 
 			IsisBoot.class, 
 			JdoTestDomainModule.class, 
+			IsisPlatformTransactionManagerForJdo.class,
 	},
 	properties = {
 		"logging.config=log4j2-test.xml",
diff --git a/example/misc/smoketest/src/test/resources/org/apache/isis/testdomain/tests/builtin-IsisBoot.json b/example/misc/smoketest/src/test/resources/org/apache/isis/testdomain/tests/builtin-IsisBoot.json
index 3e92663..034a034 100644
--- a/example/misc/smoketest/src/test/resources/org/apache/isis/testdomain/tests/builtin-IsisBoot.json
+++ b/example/misc/smoketest/src/test/resources/org/apache/isis/testdomain/tests/builtin-IsisBoot.json
@@ -23,7 +23,6 @@
   "org.apache.isis.core.runtime.services.menubars.MenuBarsLoaderServiceDefault",
   "org.apache.isis.core.runtime.services.menubars.bootstrap3.MenuBarsServiceBS3",
   "org.apache.isis.core.runtime.services.message.MessageServiceDefault",
-  "org.apache.isis.core.runtime.services.xactn.TransactionServiceDefault",
   "org.apache.isis.core.runtime.services.xmlsnapshot.XmlSnapshotServiceDefault",
   "org.apache.isis.core.security.authentication.standard.AuthenticationManagerStandard",
   "org.apache.isis.core.wrapper.WrapperFactoryDefault"