You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2017/12/07 15:01:12 UTC

[isis] 16/18: isis-1782: (WIP) factors out IsisSystemBootstrapper, and IsisSystem (from IsisSystemForTest)

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

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

commit a2b53a0a2ac06e8b78d1823879b07a284d896c0e
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu Dec 7 13:25:53 2017 +0000

    isis-1782: (WIP) factors out IsisSystemBootstrapper, and IsisSystem (from IsisSystemForTest)
    
    ... with idea being that this can be reused by BDD glue also.
---
 .../apache/isis/applib/AppManifestAbstract2.java   |  32 +-
 .../isis/applib/fixturescripts/FixtureScripts.java |  50 +-
 .../integtestsupport/IntegrationTestAbstract3.java | 230 ++-------
 .../{IsisSystemForTest.java => IsisSystem.java}    | 152 ++----
 .../integtestsupport/IsisSystemBootstrapper.java   | 221 ++++++++
 .../core/integtestsupport/IsisSystemForTest.java   | 575 +--------------------
 ...isSystemBootstrapper_haveSameModules_Test.java} |   8 +-
 .../scenarios/DomainServiceProvider.java           |   6 +-
 .../bdd/specglue/BootstrappingGlue.java            |  31 +-
 ...ingGlue.java => BootstrappingGlueAbstract.java} |  14 +-
 10 files changed, 416 insertions(+), 903 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/AppManifestAbstract2.java b/core/applib/src/main/java/org/apache/isis/applib/AppManifestAbstract2.java
index 9facefd..caa35d1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/AppManifestAbstract2.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/AppManifestAbstract2.java
@@ -36,16 +36,6 @@ public abstract class AppManifestAbstract2 extends AppManifestAbstract implement
 
         private Builder2(Module module) {
             this.module = module;
-
-            final List<Module> transitiveDependencies = Module.Util.transitiveDependenciesOf(module);
-            final Class[] moduleTransitiveDependencies = asClasses(transitiveDependencies);
-
-            final List<Class<?>> additionalModules = Module.Util.transitiveDependenciesAsClassOf(module);
-            final List<Class<?>> additionalServices = Module.Util.transitiveAdditionalServicesOf(module);
-
-            withAdditionalModules(moduleTransitiveDependencies);
-            withAdditionalModules(additionalModules);
-            withAdditionalServices(additionalServices);
         }
 
         public static Builder2 forModule(Module module) {
@@ -63,9 +53,22 @@ public abstract class AppManifestAbstract2 extends AppManifestAbstract implement
 
         @Override
         public AppManifest build() {
-            return new AppManifestAbstract2(this) {};
-        }
+            if(module instanceof ModuleAbstract) {
+                return new AppManifestAbstract2((ModuleAbstract) module){};
+            } else {
+                final List<Module> transitiveDependencies = Module.Util.transitiveDependenciesOf(module);
+                final Class[] moduleTransitiveDependencies = asClasses(transitiveDependencies);
+
+                final List<Class<?>> additionalModules = Module.Util.transitiveDependenciesAsClassOf(module);
+                final List<Class<?>> additionalServices = Module.Util.transitiveAdditionalServicesOf(module);
 
+                withAdditionalModules(moduleTransitiveDependencies);
+                withAdditionalModules(additionalModules);
+                withAdditionalServices(additionalServices);
+
+                return new AppManifestAbstract2(this) {};
+            }
+        }
     }
 
     private final Module module;
@@ -78,6 +81,11 @@ public abstract class AppManifestAbstract2 extends AppManifestAbstract implement
         this.module = builder2.module;
     }
 
+    public <M extends Module & AppManifestBuilder<?>> AppManifestAbstract2(final M module) {
+        super(module);
+        this.module = module;
+    }
+
     @Programmatic
     public Module getModule() {
         return module;
diff --git a/core/applib/src/main/java/org/apache/isis/applib/fixturescripts/FixtureScripts.java b/core/applib/src/main/java/org/apache/isis/applib/fixturescripts/FixtureScripts.java
index 6defa2b..1a10118 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/fixturescripts/FixtureScripts.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/fixturescripts/FixtureScripts.java
@@ -40,13 +40,14 @@ import org.apache.isis.applib.annotation.Parameter;
 import org.apache.isis.applib.annotation.ParameterLayout;
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.annotation.RestrictTo;
-import org.apache.isis.applib.services.bookmark.BookmarkService;
 import org.apache.isis.applib.services.classdiscovery.ClassDiscoveryService;
 import org.apache.isis.applib.services.classdiscovery.ClassDiscoveryService2;
 import org.apache.isis.applib.services.fixturespec.FixtureScriptsDefault;
 import org.apache.isis.applib.services.fixturespec.FixtureScriptsSpecification;
 import org.apache.isis.applib.services.memento.MementoService;
 import org.apache.isis.applib.services.memento.MementoService.Memento;
+import org.apache.isis.applib.services.registry.ServiceRegistry2;
+import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.applib.util.ObjectContracts;
 
 /**
@@ -385,7 +386,7 @@ public abstract class FixtureScripts extends AbstractService {
         // if this method is called programmatically, the caller may have simply new'd up the fixture script
         // (rather than use container.newTransientInstance(...).  To allow this use case, we need to ensure that
         // domain services are injected into the fixture script.
-        getContainer().injectServicesInto(fixtureScript);
+        serviceRegistry.injectServicesInto(fixtureScript);
 
         return fixtureScript.run(parameters);
     }
@@ -418,6 +419,38 @@ public abstract class FixtureScripts extends AbstractService {
     //endregion
 
     //region > programmatic API
+
+    @Programmatic
+    public void runFixtureScript(final FixtureScript... fixtureScriptList) {
+        if (fixtureScriptList.length == 1) {
+            runFixtureScript(fixtureScriptList[0], null);
+        } else {
+            runFixtureScript(new FixtureScript() {
+                protected void execute(ExecutionContext executionContext) {
+                    FixtureScript[] fixtureScripts = fixtureScriptList;
+                    for (FixtureScript fixtureScript : fixtureScripts) {
+                        executionContext.executeChild(this, fixtureScript);
+                    }
+                }
+            }, null);
+        }
+
+        transactionService.nextTransaction();
+    }
+
+    @Programmatic
+    public <T,F extends BuilderScriptAbstract<T,F>> T runBuilderScript(final F fixtureScript) {
+
+        serviceRegistry.injectServicesInto(fixtureScript);
+
+        fixtureScript.run(null);
+
+        final T object = fixtureScript.getObject();
+        transactionService.nextTransaction();
+
+        return object;
+    }
+
     @Programmatic
     public FixtureScript findFixtureScriptFor(final Class<? extends FixtureScript> fixtureScriptClass) {
         final List<FixtureScript> fixtureScripts = getFixtureScriptList();
@@ -511,16 +544,19 @@ public abstract class FixtureScripts extends AbstractService {
     //region > injected services
 
     @javax.inject.Inject
-    private MementoService mementoService;
-    
+    MementoService mementoService;
+
+    @javax.inject.Inject
+    TransactionService transactionService;
+
     @javax.inject.Inject
-    private BookmarkService bookmarkService;
+    ClassDiscoveryService classDiscoveryService;
 
     @javax.inject.Inject
-    private ClassDiscoveryService classDiscoveryService;
+    ExecutionParametersService executionParametersService;
 
     @javax.inject.Inject
-    private ExecutionParametersService executionParametersService;
+    ServiceRegistry2 serviceRegistry;
 
     //endregion
 
diff --git a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3.java b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3.java
index 4b40f5a..e157307 100644
--- a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3.java
+++ b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3.java
@@ -20,16 +20,11 @@ package org.apache.isis.core.integtestsupport;
 
 import java.io.PrintStream;
 import java.util.List;
-import java.util.UUID;
 
-import javax.annotation.Nullable;
 import javax.inject.Inject;
-import javax.jdo.PersistenceManagerFactory;
 
-import com.google.common.base.Function;
 import com.google.common.base.Strings;
 import com.google.common.base.Throwables;
-import com.google.common.collect.FluentIterable;
 
 import org.apache.log4j.PropertyConfigurator;
 import org.joda.time.LocalDate;
@@ -45,7 +40,6 @@ import org.slf4j.LoggerFactory;
 import org.slf4j.event.Level;
 
 import org.apache.isis.applib.AppManifest;
-import org.apache.isis.applib.AppManifestAbstract2;
 import org.apache.isis.applib.Module;
 import org.apache.isis.applib.NonRecoverableException;
 import org.apache.isis.applib.RecoverableException;
@@ -68,9 +62,6 @@ import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.core.commons.factory.InstanceUtil;
 import org.apache.isis.core.integtestsupport.logging.LogConfig;
 import org.apache.isis.core.integtestsupport.logging.LogStream;
-import org.apache.isis.core.runtime.system.context.IsisContext;
-import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
-import org.apache.isis.objectstore.jdo.datanucleus.IsisConfigurationForJdoIntegTests;
 
 /**
  * Reworked base class for integration tests, uses a {@link Module} to bootstrap, rather than an {@link AppManifest}.
@@ -79,6 +70,8 @@ public abstract class IntegrationTestAbstract3 {
 
     private static final Logger LOG = LoggerFactory.getLogger(IntegrationTestAbstract3.class);
     private final LogConfig logConfig;
+    private final IsisSystemBootstrapper isisSystemBootstrapper;
+
 
     protected static PrintStream logPrintStream() {
         return logPrintStream(Level.DEBUG);
@@ -111,8 +104,10 @@ public abstract class IntegrationTestAbstract3 {
     private Long t0;
     public IntegrationTestAbstract3(
             final LogConfig logConfig,
-            final Module module, final Class... additionalModuleClasses) {
+            final Module module,
+            final Class... additionalModuleClasses) {
         this.logConfig = logConfig;
+
         final boolean firstTime = !setupLogging.get();
         if(firstTime) {
             PropertyConfigurator.configure(logConfig.getLoggingPropertyFile());
@@ -130,26 +125,7 @@ public abstract class IntegrationTestAbstract3 {
             this.module = module;
             this.additionalModuleClasses = additionalModuleClasses;
         }
-    }
-
-    private void log(final String message) {
-        switch (logConfig.getTestLoggingLevel()) {
-        case ERROR:
-            LOG.error(message);
-            break;
-        case WARN:
-            LOG.warn(message);
-            break;
-        case INFO:
-            LOG.info(message);
-            break;
-        case DEBUG:
-            LOG.debug(message);
-            break;
-        case TRACE:
-            LOG.trace(message);
-            break;
-        }
+        this.isisSystemBootstrapper = new IsisSystemBootstrapper(logConfig, module, additionalModuleClasses);
     }
 
     private LocalDate timeBeforeTest;
@@ -159,116 +135,18 @@ public abstract class IntegrationTestAbstract3 {
 
         System.setProperty("isis.integTest", "true");
 
-        bootstrapIfRequired();
-
-        if(t0 != null) {
-            long t1 = System.currentTimeMillis();
-            log("##########################################################################");
-            log("# Bootstrapped in " + (t1- t0) + " millis");
-            log("##########################################################################");
-        }
-        log("### TEST: " + this.getClass().getCanonicalName());
+        isisSystemBootstrapper.bootstrapIfRequired(t0);
+        isisSystemBootstrapper.injectServicesInto(this);
 
         beginTransaction();
 
-        timeBeforeTest = Clock.getTimeAsLocalDate();
-
-        setupModuleRefData();
-    }
-
-    private void bootstrapIfRequired() {
-
-        final AppManifestAbstract2.Builder2 builder =
-                AppManifestAbstract2.Builder2.forModule(module);
-        builder.withAdditionalModules(additionalModuleClasses); // eg fake module, as passed into constructor
-
-        final AppManifestAbstract2 appManifest = (AppManifestAbstract2) builder.build();
-
-        bootstrapUsing(appManifest);
-    }
-
-    /**
-     * The {@link AppManifest} used to bootstrap the {@link IsisSystemForTest} (on the thread-local)
-     */
-    private static ThreadLocal<AppManifest> isftAppManifest = new ThreadLocal<>();
-
-    private void bootstrapUsing(AppManifest appManifest) {
-
-        final SystemState systemState = determineSystemState(appManifest);
-        switch (systemState) {
-
-        case BOOTSTRAPPED_SAME_MODULES:
-            // nothing to do
-            break;
-        case BOOTSTRAPPED_DIFFERENT_MODULES:
-            // TODO: this doesn't work correctly yet;
-            teardownSystem();
-            setupSystem(appManifest);
-            break;
-        case NOT_BOOTSTRAPPED:
-            setupSystem(appManifest);
-            TickingFixtureClock.replaceExisting();
-            break;
-        }
-    }
-
-    private static void teardownSystem() {
-        final IsisSessionFactory isisSessionFactory = IsisSystemForTest.get().getService(IsisSessionFactory.class);
-
-        // TODO: this ought to be part of isisSessionFactory's responsibilities
-        final IsisJdoSupport isisJdoSupport = isisSessionFactory.getServicesInjector()
-                .lookupService(IsisJdoSupport.class);
-        final PersistenceManagerFactory pmf =
-                isisJdoSupport.getJdoPersistenceManager().getPersistenceManagerFactory();
-        isisSessionFactory.destroyServicesAndShutdown();
-        pmf.close();
-
-        IsisContext.testReset();
-    }
-
-    private static void setupSystem(final AppManifest appManifest) {
-
-        final IsisConfigurationForJdoIntegTests configuration = new IsisConfigurationForJdoIntegTests();
-        configuration.putDataNucleusProperty("javax.jdo.option.ConnectionURL","jdbc:hsqldb:mem:test-" + UUID.randomUUID().toString());
-        final IsisSystemForTest.Builder isftBuilder =
-                new IsisSystemForTest.Builder()
-                        .withLoggingAt(org.apache.log4j.Level.INFO)
-                        .with(appManifest)
-                        .with(configuration);
+        isisSystemBootstrapper.setupModuleRefData();
 
-        IsisSystemForTest isft = isftBuilder.build();
-        isft.setUpSystem();
-
-        // save both the system and the manifest
-        // used to bootstrap the system onto thread-loca
-        IsisSystemForTest.set(isft);
-        isftAppManifest.set(appManifest);
-    }
+        log("### TEST: " + this.getClass().getCanonicalName());
 
-    enum SystemState {
-        NOT_BOOTSTRAPPED,
-        BOOTSTRAPPED_SAME_MODULES,
-        BOOTSTRAPPED_DIFFERENT_MODULES
+        timeBeforeTest = Clock.getTimeAsLocalDate();
     }
 
-    private static SystemState determineSystemState(final AppManifest appManifest) {
-        IsisSystemForTest isft = IsisSystemForTest.getElseNull();
-        if (isft == null)
-            return SystemState.NOT_BOOTSTRAPPED;
-
-        final AppManifest appManifestFromPreviously = isftAppManifest.get();
-        return haveSameModules(appManifest, appManifestFromPreviously)
-                ? SystemState.BOOTSTRAPPED_SAME_MODULES
-                : SystemState.BOOTSTRAPPED_DIFFERENT_MODULES;
-    }
-
-    static boolean haveSameModules(
-            final AppManifest m1,
-            final AppManifest m2) {
-        final List<Class<?>> m1Modules = m1.getModules();
-        final List<Class<?>> m2Modules = m2.getModules();
-        return m1Modules.containsAll(m2Modules) && m2Modules.containsAll(m1Modules);
-    }
 
     private static class IsisTransactionRule implements MethodRule {
 
@@ -283,7 +161,7 @@ public abstract class IntegrationTestAbstract3 {
                     // Instead we expect it to be bootstrapped via @Before
                     try {
                         base.evaluate();
-                        final IsisSystemForTest isft = IsisSystemForTest.get();
+                        final IsisSystem isft = IsisSystem.get();
                         isft.endTran();
                     } catch(final Throwable e) {
                         // determine if underlying cause is an applib-defined exception,
@@ -294,7 +172,7 @@ public abstract class IntegrationTestAbstract3 {
 
                         if(recoverableException != null) {
                             try {
-                                final IsisSystemForTest isft = IsisSystemForTest.get();
+                                final IsisSystem<?> isft = IsisSystem.get();
                                 isft.getContainer().flush(); // don't care if npe
                                 isft.getService(IsisJdoSupport.class).getJdoPersistenceManager().flush();
                             } catch (Exception ignore) {
@@ -303,7 +181,7 @@ public abstract class IntegrationTestAbstract3 {
                         }
                         // attempt to close this
                         try {
-                            final IsisSystemForTest isft = IsisSystemForTest.getElseNull();
+                            final IsisSystem<?> isft = IsisSystem.getElseNull();
                             isft.closeSession(); // don't care if npe
                         } catch(Exception ignore) {
                             // ignore
@@ -311,7 +189,7 @@ public abstract class IntegrationTestAbstract3 {
 
                         // attempt to start another
                         try {
-                            final IsisSystemForTest isft = IsisSystemForTest.getElseNull();
+                            final IsisSystem<?> isft = IsisSystem.getElseNull();
                             isft.openSession(); // don't care if npe
                         } catch(Exception ignore) {
                             // ignore
@@ -361,20 +239,10 @@ public abstract class IntegrationTestAbstract3 {
     }
 
     private void beginTransaction() {
-        final IsisSystemForTest isft = IsisSystemForTest.get();
-
-        isft.getContainer().injectServicesInto(this);
+        final IsisSystem isft = IsisSystem.get();
         isft.beginTran();
     }
 
-    @Inject
-    MetaModelService4 metaModelService4;
-
-    protected void setupModuleRefData() {
-        FixtureScript refDataSetupFixture = metaModelService4.getAppManifest2().getRefDataSetupFixture();
-        runFixtureScript(refDataSetupFixture);
-    }
-
     @After
     public void tearDownAllModules() {
 
@@ -386,55 +254,23 @@ public abstract class IntegrationTestAbstract3 {
 
         transactionService.nextTransaction();
 
-        FixtureScript fixtureScript = metaModelService4.getAppManifest2().getTeardownFixture();
-        runFixtureScript(fixtureScript);
+        isisSystemBootstrapper.tearDownAllModules();
 
         // reinstate clock
         setFixtureClockDate(timeBeforeTest);
     }
 
-    protected void runFixtureScript(final FixtureScript... fixtureScriptList) {
-        if (fixtureScriptList.length == 1) {
-            this.fixtureScripts.runFixtureScript(fixtureScriptList[0], null);
-        } else {
-            this.fixtureScripts.runFixtureScript(new FixtureScript() {
-                protected void execute(ExecutionContext executionContext) {
-                    FixtureScript[] fixtureScripts = fixtureScriptList;
-                    for (FixtureScript fixtureScript : fixtureScripts) {
-                        executionContext.executeChild(this, fixtureScript);
-                    }
-                }
-            }, null);
-        }
 
-        transactionService.nextTransaction();
+    protected void runFixtureScript(final FixtureScript... fixtureScriptList) {
+        this.fixtureScripts.runFixtureScript(fixtureScriptList);
     }
 
 
-    protected <T,F extends BuilderScriptAbstract<T,F>> T runBuilderScript(final F fixture) {
-
-        serviceRegistry.injectServicesInto(fixture);
-
-        fixture.run(null);
-
-
-        final T object = fixture.getObject();
-        transactionService.nextTransaction();
-
-        return object;
+    protected <T,F extends BuilderScriptAbstract<T,F>> T runBuilderScript(final F fixtureScript) {
+        return this.fixtureScripts.runBuilderScript(fixtureScript);
     }
 
 
-    private static Class[] asClasses(final List<Module> dependencies) {
-        final List<? extends Class<? extends Module>> dependenciesAsClasses =
-                FluentIterable.from(dependencies).transform(new Function<Module, Class<? extends Module>>() {
-                    @Nullable @Override public Class apply(@Nullable final Module module) {
-                        return module.getClass();
-                    }
-                }).toList();
-        return dependenciesAsClasses.toArray(new Class[] {});
-    }
-
     /**
      * For convenience of subclasses, remove some boilerplate
      */
@@ -454,6 +290,9 @@ public abstract class IntegrationTestAbstract3 {
      * To use instead of {@link #getFixtureClock()}'s {@link FixtureClock#setDate(int, int, int)} ()}.
      */
     protected void setFixtureClockDate(final LocalDate date) {
+        if(date == null) {
+            return;
+        }
         setFixtureClockDate(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth());
     }
 
@@ -482,6 +321,25 @@ public abstract class IntegrationTestAbstract3 {
     }
 
 
+    private void log(final String message) {
+        switch (logConfig.getTestLoggingLevel()) {
+        case ERROR:
+            LOG.error(message);
+            break;
+        case WARN:
+            LOG.warn(message);
+            break;
+        case INFO:
+            LOG.info(message);
+            break;
+        case DEBUG:
+            LOG.debug(message);
+            break;
+        case TRACE:
+            LOG.trace(message);
+            break;
+        }
+    }
 
     /**
      * For convenience of subclasses, remove some boilerplate
@@ -491,6 +349,8 @@ public abstract class IntegrationTestAbstract3 {
     }
 
     @Inject
+    protected MetaModelService4 metaModelService4;
+    @Inject
     protected FixtureScripts fixtureScripts;
     @Inject
     protected FactoryService factoryService;
diff --git a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemForTest.java b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystem.java
similarity index 81%
copy from core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemForTest.java
copy to core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystem.java
index e39f952..2a28a1e 100644
--- a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemForTest.java
+++ b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystem.java
@@ -25,10 +25,6 @@ import java.util.Set;
 import com.google.common.base.Joiner;
 import com.google.common.collect.Lists;
 
-import org.junit.Before;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
 import org.apache.isis.applib.AppManifest;
 import org.apache.isis.applib.DomainObjectContainer;
 import org.apache.isis.applib.fixtures.FixtureClock;
@@ -54,38 +50,37 @@ import org.apache.isis.core.runtime.system.transaction.IsisTransaction.State;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
 import org.apache.isis.core.runtime.systemusinginstallers.IsisComponentProvider;
 import org.apache.isis.core.security.authentication.AuthenticationRequestNameOnly;
-import org.apache.isis.core.specsupport.scenarios.DomainServiceProvider;
 
 import static org.junit.Assert.fail;
 
 /**
- * Wraps a plain {@link IsisSessionFactoryBuilder}, and provides a number of features to assist with testing.
+ * Wraps a plain {@link IsisSessionFactoryBuilder}.
  */
-public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServiceProvider {
+public class IsisSystem<S extends IsisSystem> {
 
     //region > Listener, ListenerAdapter
     public interface Listener {
 
         void init(IsisConfiguration configuration) throws Exception;
-        
+
         void preOpenSession(boolean firstTime) throws Exception;
         void postOpenSession(boolean firstTime) throws Exception;
-        
+
         void preNextSession() throws Exception;
         void postNextSession() throws Exception;
 
         void preCloseSession() throws Exception;
         void postCloseSession() throws Exception;
     }
-    
+
     public static abstract class ListenerAdapter implements Listener {
-        
+
         private IsisConfiguration configuration;
 
         public void init(IsisConfiguration configuration) throws Exception {
             this.configuration = configuration;
         }
-        
+
         protected IsisConfiguration getConfiguration() {
             return configuration;
         }
@@ -119,22 +114,22 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
 
     //region > getElseNull, get, set
 
-    private static ThreadLocal<IsisSystemForTest> ISFT = new ThreadLocal<>();
+    protected static ThreadLocal<IsisSystem> ISFT = new ThreadLocal<>();
 
-    public static IsisSystemForTest getElseNull() {
+    public static IsisSystem getElseNull() {
         return ISFT.get();
     }
-    
-    public static IsisSystemForTest get() {
-        final IsisSystemForTest isft = ISFT.get();
+
+    public static IsisSystem get() {
+        final IsisSystem isft = ISFT.get();
         if(isft == null) {
-            throw new IllegalStateException("No IsisSystemForTest available on thread; call #set(IsisSystemForTest) first");
+            throw new IllegalStateException("No IsisSystem available on thread; call #set(IsisSystem) first");
         }
 
         return isft;
     }
 
-    public static void set(IsisSystemForTest isft) {
+    public static void set(IsisSystem isft) {
         ISFT.set(isft);
     }
     //endregion
@@ -142,64 +137,65 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
     //region > Builder
 
 
-    public static class Builder {
-
-        private AuthenticationRequest authenticationRequest = new AuthenticationRequestNameOnly("tester");
+    public static class Builder<T extends Builder<T, S>, S extends IsisSystem> {
 
-        private IsisConfigurationDefault configuration = new IsisConfigurationDefault();
+        protected AuthenticationRequest authenticationRequest = new AuthenticationRequestNameOnly("tester");
 
-        private AppManifest appManifestIfAny;
+        protected IsisConfigurationDefault configuration = new IsisConfigurationDefault();
 
-        private final List<Object> services = Lists.newArrayList();
-        private final List<InstallableFixture> fixtures = Lists.newArrayList();
+        protected AppManifest appManifestIfAny;
 
-        private final List <Listener> listeners = Lists.newArrayList();
+        protected final List <Listener> listeners = Lists.newArrayList();
 
-        private org.apache.log4j.Level level;
+        protected org.apache.log4j.Level level;
 
-        public Builder with(IsisConfiguration configuration) {
+        public T with(IsisConfiguration configuration) {
             this.configuration = (IsisConfigurationDefault) configuration;
-            return this;
+            return (T)this;
         }
 
-        public Builder with(AuthenticationRequest authenticationRequest) {
+        public T with(AuthenticationRequest authenticationRequest) {
             this.authenticationRequest = authenticationRequest;
-            return this;
+            return (T)this;
         }
 
-        public Builder with(AppManifest appManifest) {
+        public T with(AppManifest appManifest) {
             this.appManifestIfAny = appManifest;
-            return this;
+            return (T)this;
         }
 
-        public Builder withLoggingAt(org.apache.log4j.Level level) {
+        public T withLoggingAt(org.apache.log4j.Level level) {
             this.level = level;
-            return this;
+            return (T)this;
         }
 
-        public IsisSystemForTest build() {
-            final IsisSystemForTest isisSystemForTest =
-                    new IsisSystemForTest(
+        public S build() {
+            final IsisSystem isisSystem =
+                    new IsisSystem(
                             appManifestIfAny,
                             configuration,
                             authenticationRequest,
                             listeners);
+            return (S)configure(isisSystem);
+        }
+
+        protected <T extends IsisSystem> T configure(final T isisSystem) {
             if(level != null) {
-                isisSystemForTest.setLevel(level);
+                isisSystem.setLevel(level);
             }
 
             Runtime.getRuntime().addShutdownHook(new Thread() {
                 @Override
                 public synchronized void run() {
                     try {
-                        isisSystemForTest.closeSession();
+                        isisSystem.closeSession();
                     } catch (Exception e) {
                         e.printStackTrace();
                     }
 
                     try {
-                        if(isisSystemForTest.isisSessionFactory != null) {
-                            isisSystemForTest.isisSessionFactory.destroyServicesAndShutdown();
+                        if(isisSystem.isisSessionFactory != null) {
+                            isisSystem.isisSessionFactory.destroyServicesAndShutdown();
                         }
                     } catch (Exception e) {
                         e.printStackTrace();
@@ -207,10 +203,9 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
                 }
             });
 
-            return isisSystemForTest;
+            return isisSystem;
         }
 
-
         public Builder with(Listener listener) {
             if(listener != null) {
                 listeners.add(listener);
@@ -229,14 +224,14 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
     //region > constructor, fields
 
     // these fields 'xxxForComponentProvider' are used to initialize the IsisComponentProvider, but shouldn't be used thereafter.
-    private final AppManifest appManifestIfAny;
-    private final IsisConfiguration configurationOverride;
+    protected final AppManifest appManifestIfAny;
+    protected final IsisConfiguration configurationOverride;
 
-    private final AuthenticationRequest authenticationRequestIfAny;
-    private AuthenticationSession authenticationSession;
+    protected final AuthenticationRequest authenticationRequestIfAny;
+    protected AuthenticationSession authenticationSession;
 
 
-    private IsisSystemForTest(
+    protected IsisSystem(
             final AppManifest appManifestIfAny,
             final IsisConfiguration configurationOverride,
             final AuthenticationRequest authenticationRequestIfAny,
@@ -258,7 +253,7 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
     public org.apache.log4j.Level getLevel() {
         return level;
     }
-    
+
     public void setLevel(org.apache.log4j.Level level) {
         this.level = level;
     }
@@ -268,21 +263,18 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
     //region > setup (also componentProvider)
 
     // populated at #setupSystem
-    private IsisComponentProvider componentProvider;
+    protected IsisComponentProvider componentProvider;
 
-    /**
-     * Intended to be called from a test's {@link Before} method.
-     */
-    public IsisSystemForTest setUpSystem() throws RuntimeException {
+    S setUpSystem() throws RuntimeException {
         try {
             initIfRequiredThenOpenSession(FireListeners.FIRE);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
-        return this;
+        return (S)this;
     }
 
-    private void initIfRequiredThenOpenSession(FireListeners fireListeners) throws Exception {
+    protected void initIfRequiredThenOpenSession(FireListeners fireListeners) throws Exception {
 
         // exit as quickly as possible for this case...
         final MetaModelInvalidException mmie = IsisContext.getMetaModelInvalidExceptionIfAny();
@@ -351,7 +343,7 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
     //endregion
 
     //region > isisSystem (populated during setup)
-    private IsisSessionFactory isisSessionFactory;
+    protected IsisSessionFactory isisSessionFactory;
 
     /**
      * The {@link IsisSessionFactory} created during {@link #setUpSystem()}.
@@ -412,7 +404,7 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
 
     private List <Listener> listeners;
 
-    private enum FireListeners {
+    protected enum FireListeners {
         FIRE,
         DONT_FIRE;
         public boolean shouldFire() {
@@ -463,30 +455,6 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
     }
     //endregion
 
-    //region > JUnit @Rule integration
-
-    @Override
-    public Statement apply(final Statement base, final Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                setUpSystem();
-                try {
-                    base.evaluate();
-                    closeSession();
-                } catch(Throwable ex) {
-                    try {
-                        closeSession();
-                    } catch(Exception ex2) {
-                        // ignore, since already one pending
-                    }
-                    throw ex;
-                }
-            }
-        };
-    }
-
-    //endregion
 
     //region > beginTran, endTran, commitTran, abortTran
 
@@ -531,7 +499,7 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
     }
 
     /**
-     * Either commits or aborts the transaction, depending on the Transaction's {@link org.apache.isis.core.runtime.system.transaction.IsisTransaction#getState()}
+     * Either commits or aborts the transaction, depending on the Transaction's {@link IsisTransaction#getState()}
      *
      * @deprecated - ought to be using regular domain services rather than reaching into the framework
      */
@@ -622,24 +590,14 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
 
     //endregion
 
-    //region > getService, replaceService
+    //region > getService
 
-    /* (non-Javadoc)
-     * @see org.apache.isis.core.integtestsupport.ServiceProvider#getService(java.lang.Class)
-     */
-    @Override
     @SuppressWarnings("unchecked")
-    public <T> T getService(Class<T> serviceClass) {
+    public <C> C getService(Class<C> serviceClass) {
         final ServicesInjector servicesInjector = isisSessionFactory.getServicesInjector();
         return servicesInjector.lookupServiceElseFail(serviceClass);
     }
 
-    @Override
-    public <T> void replaceService(final T originalService, final T replacementService) {
-        final ServicesInjector servicesInjector = isisSessionFactory.getServicesInjector();
-        servicesInjector.replaceService(originalService, replacementService);
-    }
-
     //endregion
 
     //region > Fixture management (for each test, rather than at bootstrap)
diff --git a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemBootstrapper.java b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemBootstrapper.java
new file mode 100644
index 0000000..60a5834
--- /dev/null
+++ b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemBootstrapper.java
@@ -0,0 +1,221 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.integtestsupport;
+
+import java.util.List;
+import java.util.UUID;
+
+import javax.jdo.PersistenceManagerFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.isis.applib.AppManifest;
+import org.apache.isis.applib.AppManifest2;
+import org.apache.isis.applib.AppManifestAbstract2;
+import org.apache.isis.applib.Module;
+import org.apache.isis.applib.clock.TickingFixtureClock;
+import org.apache.isis.applib.fixturescripts.FixtureScript;
+import org.apache.isis.applib.fixturescripts.FixtureScripts;
+import org.apache.isis.applib.services.jdosupport.IsisJdoSupport;
+import org.apache.isis.applib.services.metamodel.MetaModelService4;
+import org.apache.isis.applib.services.registry.ServiceRegistry2;
+import org.apache.isis.core.integtestsupport.logging.LogConfig;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
+import org.apache.isis.objectstore.jdo.datanucleus.IsisConfigurationForJdoIntegTests;
+
+public class IsisSystemBootstrapper {
+
+    private static final Logger LOG = LoggerFactory.getLogger(IsisSystemBootstrapper.class);
+
+    /**
+     * The {@link AppManifest2} used to bootstrap the {@link IsisSystem} (on the thread-local)
+     */
+    private static ThreadLocal<AppManifest2> isftAppManifest = new ThreadLocal<>();
+
+
+    private final LogConfig logConfig;
+    private final Module module;
+    private final Class[] additionalModuleClasses;
+
+    public IsisSystemBootstrapper(
+            final LogConfig logConfig,
+            final Module module,
+            final Class... additionalModuleClasses) {
+
+        this.logConfig = logConfig;
+        this.module = module;
+        this.additionalModuleClasses = additionalModuleClasses;
+    }
+
+    public void bootstrapIfRequired(final Long t0) {
+
+        final AppManifestAbstract2.Builder2 builder =
+                AppManifestAbstract2.Builder2.forModule(module);
+        builder.withAdditionalModules(additionalModuleClasses); // eg fake module, as passed into constructor
+
+        final AppManifestAbstract2 appManifest = (AppManifestAbstract2) builder.build();
+
+        bootstrapUsing(appManifest, t0);
+    }
+
+    /**
+     * Expects a transaction to have been started
+     */
+    public void setupModuleRefData() {
+        MetaModelService4 metaModelService4 = lookupService(MetaModelService4.class);
+        FixtureScript refDataSetupFixture = metaModelService4.getAppManifest2().getRefDataSetupFixture();
+        runFixtureScript(refDataSetupFixture);
+    }
+
+
+    private void bootstrapUsing(AppManifest2 appManifest2, Long t0) {
+
+        final SystemState systemState = determineSystemState(appManifest2);
+        switch (systemState) {
+
+        case BOOTSTRAPPED_SAME_MODULES:
+            // nothing to do
+            break;
+        case BOOTSTRAPPED_DIFFERENT_MODULES:
+            // TODO: this doesn't work correctly yet; not tearing down HSQLDB correctly.
+            teardownSystem();
+            // fall through
+        case NOT_BOOTSTRAPPED:
+            setupSystem(appManifest2);
+            TickingFixtureClock.replaceExisting();
+
+            if(t0 != null) {
+                long t1 = System.currentTimeMillis();
+                log("##########################################################################");
+                log("# Bootstrapped in " + (t1- t0) + " millis");
+                log("##########################################################################");
+            }
+
+            break;
+        }
+    }
+
+    private static SystemState determineSystemState(final AppManifest appManifest) {
+        IsisSystem isft = IsisSystem.getElseNull();
+        if (isft == null)
+            return SystemState.NOT_BOOTSTRAPPED;
+
+        final AppManifest appManifestFromPreviously = isftAppManifest.get();
+        return haveSameModules(appManifest, appManifestFromPreviously)
+                ? SystemState.BOOTSTRAPPED_SAME_MODULES
+                : SystemState.BOOTSTRAPPED_DIFFERENT_MODULES;
+    }
+
+    static boolean haveSameModules(
+            final AppManifest m1,
+            final AppManifest m2) {
+        final List<Class<?>> m1Modules = m1.getModules();
+        final List<Class<?>> m2Modules = m2.getModules();
+        return m1Modules.containsAll(m2Modules) && m2Modules.containsAll(m1Modules);
+    }
+
+    private static IsisSystem setupSystem(final AppManifest2 appManifest2) {
+
+        final IsisConfigurationForJdoIntegTests configuration = new IsisConfigurationForJdoIntegTests();
+        configuration.putDataNucleusProperty("javax.jdo.option.ConnectionURL","jdbc:hsqldb:mem:test-" + UUID.randomUUID().toString());
+        final IsisSystem.Builder isftBuilder =
+                new IsisSystem.Builder()
+                        .withLoggingAt(org.apache.log4j.Level.INFO)
+                        .with(appManifest2)
+                        .with(configuration);
+
+        IsisSystem isft = isftBuilder.build();
+        isft.setUpSystem();
+
+        // save both the system and the manifest
+        // used to bootstrap the system onto thread-loca
+        IsisSystem.set(isft);
+        isftAppManifest.set(appManifest2);
+
+        return isft;
+    }
+
+    public void injectServicesInto(final Object object) {
+        lookupService(ServiceRegistry2.class).injectServicesInto(object);
+    }
+
+    enum SystemState {
+        NOT_BOOTSTRAPPED,
+        BOOTSTRAPPED_SAME_MODULES,
+        BOOTSTRAPPED_DIFFERENT_MODULES
+    }
+
+    private static void teardownSystem() {
+        final IsisSessionFactory isisSessionFactory = lookupService(IsisSessionFactory.class);
+
+        // TODO: this ought to be part of isisSessionFactory's responsibilities
+        final IsisJdoSupport isisJdoSupport = lookupService(IsisJdoSupport.class);
+        final PersistenceManagerFactory pmf =
+                isisJdoSupport.getJdoPersistenceManager().getPersistenceManagerFactory();
+        isisSessionFactory.destroyServicesAndShutdown();
+        pmf.close();
+
+        IsisContext.testReset();
+    }
+
+    public void tearDownAllModules() {
+        final MetaModelService4 metaModelService4 = lookupService(MetaModelService4.class);
+
+        FixtureScript fixtureScript = metaModelService4.getAppManifest2().getTeardownFixture();
+        runFixtureScript(fixtureScript);
+    }
+
+
+    private void runFixtureScript(final FixtureScript... fixtureScriptList) {
+        final FixtureScripts fixtureScripts = lookupService(FixtureScripts.class);
+        fixtureScripts.runFixtureScript(fixtureScriptList);
+    }
+
+
+    private static IsisSystem<?> getIsisSystem() {
+        return IsisSystem.get();
+    }
+
+    private static <T> T lookupService(Class<T> serviceClass) {
+        return getIsisSystem().getService(serviceClass);
+    }
+
+    private void log(final String message) {
+        switch (logConfig.getTestLoggingLevel()) {
+        case ERROR:
+            LOG.error(message);
+            break;
+        case WARN:
+            LOG.warn(message);
+            break;
+        case INFO:
+            LOG.info(message);
+            break;
+        case DEBUG:
+            LOG.debug(message);
+            break;
+        case TRACE:
+            LOG.trace(message);
+            break;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemForTest.java b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemForTest.java
index e39f952..93db449 100644
--- a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemForTest.java
+++ b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IsisSystemForTest.java
@@ -20,162 +20,41 @@
 package org.apache.isis.core.integtestsupport;
 
 import java.util.List;
-import java.util.Set;
 
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-
-import org.junit.Before;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 import org.apache.isis.applib.AppManifest;
-import org.apache.isis.applib.DomainObjectContainer;
-import org.apache.isis.applib.fixtures.FixtureClock;
-import org.apache.isis.applib.fixtures.InstallableFixture;
-import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.command.CommandContext;
-import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.commons.config.IsisConfiguration;
-import org.apache.isis.core.commons.config.IsisConfigurationDefault;
-import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
 import org.apache.isis.core.metamodel.services.ServicesInjector;
-import org.apache.isis.core.metamodel.specloader.validator.MetaModelInvalidException;
-import org.apache.isis.core.runtime.authentication.AuthenticationManager;
 import org.apache.isis.core.runtime.authentication.AuthenticationRequest;
-import org.apache.isis.core.runtime.fixtures.FixturesInstallerDelegate;
-import org.apache.isis.core.runtime.logging.IsisLoggingConfigurer;
-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 org.apache.isis.core.runtime.system.session.IsisSessionFactoryBuilder;
-import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
-import org.apache.isis.core.runtime.system.transaction.IsisTransaction.State;
-import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
-import org.apache.isis.core.runtime.systemusinginstallers.IsisComponentProvider;
-import org.apache.isis.core.security.authentication.AuthenticationRequestNameOnly;
 import org.apache.isis.core.specsupport.scenarios.DomainServiceProvider;
 
-import static org.junit.Assert.fail;
-
 /**
  * Wraps a plain {@link IsisSessionFactoryBuilder}, and provides a number of features to assist with testing.
  */
-public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServiceProvider {
-
-    //region > Listener, ListenerAdapter
-    public interface Listener {
-
-        void init(IsisConfiguration configuration) throws Exception;
-        
-        void preOpenSession(boolean firstTime) throws Exception;
-        void postOpenSession(boolean firstTime) throws Exception;
-        
-        void preNextSession() throws Exception;
-        void postNextSession() throws Exception;
-
-        void preCloseSession() throws Exception;
-        void postCloseSession() throws Exception;
-    }
-    
-    public static abstract class ListenerAdapter implements Listener {
-        
-        private IsisConfiguration configuration;
-
-        public void init(IsisConfiguration configuration) throws Exception {
-            this.configuration = configuration;
-        }
-        
-        protected IsisConfiguration getConfiguration() {
-            return configuration;
-        }
-
-        @Override
-        public void preOpenSession(boolean firstTime) throws Exception {
-        }
-
-        @Override
-        public void postOpenSession(boolean firstTime) throws Exception {
-        }
-
-        @Override
-        public void preNextSession() throws Exception {
-        }
-
-        @Override
-        public void postNextSession() throws Exception {
-        }
-
-        @Override
-        public void preCloseSession() throws Exception {
-        }
-
-        @Override
-        public void postCloseSession() throws Exception {
-        }
-    }
-
-    //endregion
+public class IsisSystemForTest extends IsisSystem<IsisSystemForTest>
+        implements org.junit.rules.TestRule, DomainServiceProvider {
 
     //region > getElseNull, get, set
 
-    private static ThreadLocal<IsisSystemForTest> ISFT = new ThreadLocal<>();
-
     public static IsisSystemForTest getElseNull() {
-        return ISFT.get();
+        return (IsisSystemForTest) IsisSystem.getElseNull();
     }
-    
-    public static IsisSystemForTest get() {
-        final IsisSystemForTest isft = ISFT.get();
-        if(isft == null) {
-            throw new IllegalStateException("No IsisSystemForTest available on thread; call #set(IsisSystemForTest) first");
-        }
 
-        return isft;
+    public static IsisSystemForTest get() {
+        return (IsisSystemForTest) IsisSystem.get();
     }
 
-    public static void set(IsisSystemForTest isft) {
-        ISFT.set(isft);
+    public static void set(IsisSystem isft) {
+        IsisSystem.set(isft);
     }
     //endregion
 
     //region > Builder
 
-
-    public static class Builder {
-
-        private AuthenticationRequest authenticationRequest = new AuthenticationRequestNameOnly("tester");
-
-        private IsisConfigurationDefault configuration = new IsisConfigurationDefault();
-
-        private AppManifest appManifestIfAny;
-
-        private final List<Object> services = Lists.newArrayList();
-        private final List<InstallableFixture> fixtures = Lists.newArrayList();
-
-        private final List <Listener> listeners = Lists.newArrayList();
-
-        private org.apache.log4j.Level level;
-
-        public Builder with(IsisConfiguration configuration) {
-            this.configuration = (IsisConfigurationDefault) configuration;
-            return this;
-        }
-
-        public Builder with(AuthenticationRequest authenticationRequest) {
-            this.authenticationRequest = authenticationRequest;
-            return this;
-        }
-
-        public Builder with(AppManifest appManifest) {
-            this.appManifestIfAny = appManifest;
-            return this;
-        }
-
-        public Builder withLoggingAt(org.apache.log4j.Level level) {
-            this.level = level;
-            return this;
-        }
+    public static class Builder extends IsisSystem.Builder<IsisSystemForTest.Builder, IsisSystemForTest> {
 
         public IsisSystemForTest build() {
             final IsisSystemForTest isisSystemForTest =
@@ -184,40 +63,10 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
                             configuration,
                             authenticationRequest,
                             listeners);
-            if(level != null) {
-                isisSystemForTest.setLevel(level);
-            }
-
-            Runtime.getRuntime().addShutdownHook(new Thread() {
-                @Override
-                public synchronized void run() {
-                    try {
-                        isisSystemForTest.closeSession();
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-
-                    try {
-                        if(isisSystemForTest.isisSessionFactory != null) {
-                            isisSystemForTest.isisSessionFactory.destroyServicesAndShutdown();
-                        }
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-                }
-            });
-
-            return isisSystemForTest;
+            return configure(isisSystemForTest);
         }
 
 
-        public Builder with(Listener listener) {
-            if(listener != null) {
-                listeners.add(listener);
-            }
-            return this;
-        }
-
     }
 
     public static Builder builder() {
@@ -229,239 +78,21 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
     //region > constructor, fields
 
     // these fields 'xxxForComponentProvider' are used to initialize the IsisComponentProvider, but shouldn't be used thereafter.
-    private final AppManifest appManifestIfAny;
-    private final IsisConfiguration configurationOverride;
 
-    private final AuthenticationRequest authenticationRequestIfAny;
-    private AuthenticationSession authenticationSession;
 
-
-    private IsisSystemForTest(
+    IsisSystemForTest(
             final AppManifest appManifestIfAny,
             final IsisConfiguration configurationOverride,
             final AuthenticationRequest authenticationRequestIfAny,
             final List<Listener> listeners) {
-        this.appManifestIfAny = appManifestIfAny;
-        this.configurationOverride = configurationOverride;
-        this.authenticationRequestIfAny = authenticationRequestIfAny;
-        this.listeners = listeners;
+        super(appManifestIfAny, configurationOverride, authenticationRequestIfAny, listeners);
     }
 
     //endregion
 
-    //region > level
-    private org.apache.log4j.Level level = org.apache.log4j.Level.INFO;
-
-    /**
-     * The level to use for the root logger if fallback (ie a <tt>logging.properties</tt> file cannot be found).
-     */
-    public org.apache.log4j.Level getLevel() {
-        return level;
-    }
-    
-    public void setLevel(org.apache.log4j.Level level) {
-        this.level = level;
-    }
-
-    //endregion
 
     //region > setup (also componentProvider)
 
-    // populated at #setupSystem
-    private IsisComponentProvider componentProvider;
-
-    /**
-     * Intended to be called from a test's {@link Before} method.
-     */
-    public IsisSystemForTest setUpSystem() throws RuntimeException {
-        try {
-            initIfRequiredThenOpenSession(FireListeners.FIRE);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        return this;
-    }
-
-    private void initIfRequiredThenOpenSession(FireListeners fireListeners) throws Exception {
-
-        // exit as quickly as possible for this case...
-        final MetaModelInvalidException mmie = IsisContext.getMetaModelInvalidExceptionIfAny();
-        if(mmie != null) {
-            final Set<String> validationErrors = mmie.getValidationErrors();
-            final String validationMsg = Joiner.on("\n").join(validationErrors);
-            fail(validationMsg);
-            return;
-        }
-
-        boolean firstTime = isisSessionFactory == null;
-        if(fireListeners.shouldFire()) {
-            fireInitAndPreOpenSession(firstTime);
-        }
-
-        if(firstTime) {
-            IsisLoggingConfigurer isisLoggingConfigurer = new IsisLoggingConfigurer(getLevel());
-            isisLoggingConfigurer.configureLogging(".", new String[] {});
-
-            componentProvider = new IsisComponentProviderDefault(
-                    appManifestIfAny,
-                    configurationOverride
-            );
-
-            final IsisSessionFactoryBuilder isisSessionFactoryBuilder = new IsisSessionFactoryBuilder(componentProvider, DeploymentCategory.PRODUCTION, appManifestIfAny);
-
-            // ensures that a FixtureClock is installed as the singleton underpinning the ClockService
-            FixtureClock.initialize();
-
-            isisSessionFactory = isisSessionFactoryBuilder.buildSessionFactory();
-            // REVIEW: does no harm, but is this required?
-            closeSession();
-
-            // if the IsisSystem does not initialize properly, then - as a side effect - the resulting
-            // MetaModelInvalidException will be pushed onto the IsisContext (as a static field).
-            final MetaModelInvalidException ex = IsisContext.getMetaModelInvalidExceptionIfAny();
-            if (ex != null) {
-
-                // for subsequent tests; the attempt to bootstrap the framework will leave
-                // the IsisContext singleton as set.
-                IsisContext.testReset();
-
-                final Set<String> validationErrors = ex.getValidationErrors();
-                final StringBuilder buf = new StringBuilder();
-                for (String validationError : validationErrors) {
-                    buf.append(validationError).append("\n");
-                }
-                fail("Metamodel is invalid: \n" + buf.toString());
-            }
-        }
-
-        final AuthenticationManager authenticationManager = isisSessionFactory.getAuthenticationManager();
-        authenticationSession = authenticationManager.authenticate(authenticationRequestIfAny);
-
-        openSession();
-
-        if(fireListeners.shouldFire()) {
-            firePostOpenSession(firstTime);
-        }
-    }
-
-    public DomainObjectContainer getContainer() {
-        return getService(DomainObjectContainer.class);
-    }
-
-    //endregion
-
-    //region > isisSystem (populated during setup)
-    private IsisSessionFactory isisSessionFactory;
-
-    /**
-     * The {@link IsisSessionFactory} created during {@link #setUpSystem()}.
-     */
-    public IsisSessionFactory getIsisSessionFactory() {
-        return isisSessionFactory;
-    }
-
-    /**
-     * The {@link AuthenticationSession} created during {@link #setUpSystem()}.
-     */
-    public AuthenticationSession getAuthenticationSession() {
-        return authenticationSession;
-    }
-
-    //endregion
-
-    //region > teardown
-
-    private void closeSession(final FireListeners fireListeners) throws Exception {
-        if(fireListeners.shouldFire()) {
-            firePreCloseSession();
-        }
-        if(isisSessionFactory.inSession()) {
-            isisSessionFactory.closeSession();
-        }
-        if(fireListeners.shouldFire()) {
-            firePostCloseSession();
-        }
-    }
-
-    public void nextSession() throws Exception {
-        firePreNextSession();
-        closeSession();
-        openSession();
-        firePostNextSession();
-    }
-
-    //endregion
-
-    //region > openSession, closeSession
-    public void openSession() throws Exception {
-        openSession(authenticationSession);
-
-    }
-
-    public void openSession(AuthenticationSession authenticationSession) throws Exception {
-        isisSessionFactory.openSession(authenticationSession);
-    }
-
-    public void closeSession() throws Exception {
-        closeSession(FireListeners.FIRE);
-    }
-
-    //endregion
-
-    //region > listeners
-
-    private List <Listener> listeners;
-
-    private enum FireListeners {
-        FIRE,
-        DONT_FIRE;
-        public boolean shouldFire() {
-            return this == FIRE;
-        }
-    }
-
-
-    private void fireInitAndPreOpenSession(boolean firstTime) throws Exception {
-        if(firstTime) {
-            for(Listener listener: listeners) {
-                listener.init(componentProvider.getConfiguration());
-            }
-        }
-        for(Listener listener: listeners) {
-            listener.preOpenSession(firstTime);
-        }
-    }
-
-    private void firePostOpenSession(boolean firstTime) throws Exception {
-        for(Listener listener: listeners) {
-            listener.postOpenSession(firstTime);
-        }
-    }
-
-    private void firePreCloseSession() throws Exception {
-        for(Listener listener: listeners) {
-            listener.preCloseSession();
-        }
-    }
-
-    private void firePostCloseSession() throws Exception {
-        for(Listener listener: listeners) {
-            listener.postCloseSession();
-        }
-    }
-
-    private void firePreNextSession() throws Exception {
-        for(Listener listener: listeners) {
-            listener.preNextSession();
-        }
-    }
-
-    private void firePostNextSession() throws Exception {
-        for(Listener listener: listeners) {
-            listener.postNextSession();
-        }
-    }
-    //endregion
 
     //region > JUnit @Rule integration
 
@@ -488,152 +119,9 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
 
     //endregion
 
-    //region > beginTran, endTran, commitTran, abortTran
-
-    /**
-     * @deprecated - ought to be using regular domain services rather than reaching into the framework
-     */
-    @Deprecated
-    public void beginTran() {
-        final IsisTransactionManager transactionManager = getTransactionManager();
-        final IsisTransaction transaction = transactionManager.getCurrentTransaction();
-
-        if(transaction == null) {
-            startTransactionForUser(transactionManager);
-            return;
-        }
-
-        final State state = transaction.getState();
-        switch(state) {
-            case COMMITTED:
-            case ABORTED:
-                startTransactionForUser(transactionManager);
-                break;
-            case IN_PROGRESS:
-                // nothing to do
-                break;
-            case MUST_ABORT:
-                fail("Transaction is in state of '" + state + "'");
-                break;
-            default:
-                fail("Unknown transaction state '" + state + "'");
-        }
-
-    }
-
-    private void startTransactionForUser(IsisTransactionManager transactionManager) {
-        transactionManager.startTransaction();
-
-        // specify that this command (if any) is being executed by a 'USER'
-        final CommandContext commandContext = getService(CommandContext.class);
-        Command command = commandContext.getCommand();
-        command.setExecutor(Command.Executor.USER);
-    }
-
-    /**
-     * Either commits or aborts the transaction, depending on the Transaction's {@link org.apache.isis.core.runtime.system.transaction.IsisTransaction#getState()}
-     *
-     * @deprecated - ought to be using regular domain services rather than reaching into the framework
-     */
-    @Deprecated
-    public void endTran() {
-        final IsisTransactionManager transactionManager = getTransactionManager();
-        final IsisTransaction transaction = transactionManager.getCurrentTransaction();
-        if(transaction == null) {
-            fail("No transaction exists");
-            return;
-        }
-
-        transactionManager.endTransaction();
-
-        final State state = transaction.getState();
-        switch(state) {
-            case COMMITTED:
-                break;
-            case ABORTED:
-                break;
-            case IN_PROGRESS:
-                fail("Transaction is still in state of '" + state + "'");
-                break;
-            case MUST_ABORT:
-                fail("Transaction is still in state of '" + state + "'");
-                break;
-            default:
-                fail("Unknown transaction state '" + state + "'");
-        }
-    }
-
-    /**
-     * Commits the transaction.
-     *
-     * @deprecated - ought to be using regular domain services rather than reaching into the framework
-     */
-    @Deprecated
-    public void commitTran() {
-        final IsisTransactionManager transactionManager = getTransactionManager();
-        final IsisTransaction transaction = transactionManager.getCurrentTransaction();
-        if(transaction == null) {
-            fail("No transaction exists");
-            return;
-        }
-        final State state = transaction.getState();
-        switch(state) {
-            case COMMITTED:
-            case ABORTED:
-            case MUST_ABORT:
-                fail("Transaction is in state of '" + state + "'");
-                break;
-            case IN_PROGRESS:
-                transactionManager.endTransaction();
-                break;
-            default:
-                fail("Unknown transaction state '" + state + "'");
-        }
-    }
-
-    /**
-     * Aborts the transaction.
-     *
-     * @deprecated - ought to be using regular domain services rather than reaching into the framework
-     */
-    @Deprecated
-    public void abortTran() {
-        final IsisTransactionManager transactionManager = getTransactionManager();
-        final IsisTransaction transaction = transactionManager.getCurrentTransaction();
-        if(transaction == null) {
-            fail("No transaction exists");
-            return;
-        }
-        final State state = transaction.getState();
-        switch(state) {
-            case ABORTED:
-                break;
-            case COMMITTED:
-                fail("Transaction is in state of '" + state + "'");
-                break;
-            case MUST_ABORT:
-            case IN_PROGRESS:
-                transactionManager.abortTransaction();
-                break;
-            default:
-                fail("Unknown transaction state '" + state + "'");
-        }
-    }
-
-    //endregion
 
     //region > getService, replaceService
 
-    /* (non-Javadoc)
-     * @see org.apache.isis.core.integtestsupport.ServiceProvider#getService(java.lang.Class)
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T> T getService(Class<T> serviceClass) {
-        final ServicesInjector servicesInjector = isisSessionFactory.getServicesInjector();
-        return servicesInjector.lookupServiceElseFail(serviceClass);
-    }
-
     @Override
     public <T> void replaceService(final T originalService, final T replacementService) {
         final ServicesInjector servicesInjector = isisSessionFactory.getServicesInjector();
@@ -642,46 +130,5 @@ public class IsisSystemForTest implements org.junit.rules.TestRule, DomainServic
 
     //endregion
 
-    //region > Fixture management (for each test, rather than at bootstrap)
-
-    /**
-     * @deprecated - use {@link org.apache.isis.applib.fixturescripts.FixtureScripts} domain service instead.
-     */
-    @Deprecated
-    public void installFixtures(final InstallableFixture... fixtures) {
-        final FixturesInstallerDelegate fid = new FixturesInstallerDelegate(isisSessionFactory);
-        for (final InstallableFixture fixture : fixtures) {
-            fid.addFixture(fixture);
-        }
-        fid.installFixtures();
-
-        // ensure that tests are performed in separate xactn to any fixture setup.
-        final IsisTransactionManager transactionManager = getTransactionManager();
-        final IsisTransaction transaction = transactionManager.getCurrentTransaction();
-        final State transactionState = transaction.getState();
-        if(transactionState.canCommit()) {
-            commitTran();
-            try {
-                nextSession();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-            beginTran();
-        }
-    }
-
-    //endregion
-
-    //region > Dependencies
-
-    private IsisTransactionManager getTransactionManager() {
-        return getPersistenceSession().getTransactionManager();
-    }
-    
-    private PersistenceSession getPersistenceSession() {
-        return isisSessionFactory.getCurrentSession().getPersistenceSession();
-    }
-
-    //endregion
 
 }
diff --git a/core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3_haveSameModules_Test.java b/core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IsisSystemBootstrapper_haveSameModules_Test.java
similarity index 86%
rename from core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3_haveSameModules_Test.java
rename to core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IsisSystemBootstrapper_haveSameModules_Test.java
index 44676e9..0a2cbb2 100644
--- a/core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3_haveSameModules_Test.java
+++ b/core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IsisSystemBootstrapper_haveSameModules_Test.java
@@ -26,7 +26,7 @@ import org.apache.isis.applib.AppManifestAbstract;
 import static junit.framework.TestCase.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-public class IntegrationTestAbstract3_haveSameModules_Test {
+public class IsisSystemBootstrapper_haveSameModules_Test {
 
     public static class SomeModule{}
     public static class OtherModule{}
@@ -43,12 +43,12 @@ public class IntegrationTestAbstract3_haveSameModules_Test {
     @Test
     public void when_they_do() throws Exception {
 
-        assertTrue(IntegrationTestAbstract3.haveSameModules(m1, m2));
-        assertTrue(IntegrationTestAbstract3.haveSameModules(m1, m1_different_order));
+        assertTrue(IsisSystemBootstrapper.haveSameModules(m1, m2));
+        assertTrue(IsisSystemBootstrapper.haveSameModules(m1, m1_different_order));
     }
 
     @Test
     public void when_they_dont() throws Exception {
-        assertFalse(IntegrationTestAbstract3.haveSameModules(m1, m3));
+        assertFalse(IsisSystemBootstrapper.haveSameModules(m1, m3));
     }
 }
\ No newline at end of file
diff --git a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/DomainServiceProvider.java b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/DomainServiceProvider.java
index ca73bd5..b89c8b6 100644
--- a/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/DomainServiceProvider.java
+++ b/core/specsupport/src/main/java/org/apache/isis/core/specsupport/scenarios/DomainServiceProvider.java
@@ -30,9 +30,9 @@ import org.apache.isis.applib.DomainObjectContainer;
  */
 public interface DomainServiceProvider {
 
-    public abstract DomainObjectContainer getContainer();
+    DomainObjectContainer getContainer();
 
-    public abstract <T> T getService(Class<T> serviceClass);
+    <T> T getService(Class<T> serviceClass);
 
     /**
      * Replaces the service implementation with some other.
@@ -42,5 +42,5 @@ public interface DomainServiceProvider {
      * service implementation afterwards.
      * </p>
      */
-    public abstract <T> void replaceService(T original, T replacement);
+    <T> void replaceService(T original, T replacement);
 }
\ No newline at end of file
diff --git a/example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlue.java b/example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlue.java
index 29d13da..379afdc 100644
--- a/example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlue.java
+++ b/example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlue.java
@@ -16,37 +16,12 @@ O *  Licensed to the Apache Software Foundation (ASF) under one or more
  */
 package domainapp.application.bdd.specglue;
 
-import org.apache.isis.core.integtestsupport.IntegrationTestAbstract3;
-import org.apache.isis.core.specsupport.scenarios.ScenarioExecutionScope;
-import org.apache.isis.core.specsupport.specs.CukeGlueAbstract;
-
-import cucumber.api.java.After;
-import cucumber.api.java.Before;
 import domainapp.application.DomainAppApplicationModule;
 
-public class BootstrappingGlue extends CukeGlueAbstract {
-
-    @Before(value={"@integration"}, order=100)
-    public void beforeScenarioIntegrationScope() {
-
-        IntegrationTestAbstract3 integTest =
-                new IntegrationTestAbstract3(new DomainAppApplicationModule()) {};
-        integTest.bootstrapAndSetupIfRequired();
-
-        before(ScenarioExecutionScope.INTEGRATION);
+public class BootstrappingGlue extends BootstrappingGlueAbstract {
 
-        scenarioExecution().putVar(IntegrationTestAbstract3.class.getName(), "current", integTest);
+    public BootstrappingGlue() {
+        super(new DomainAppApplicationModule());
     }
 
-    @After
-    public void afterScenario(cucumber.api.Scenario sc) {
-        assertMocksSatisfied();
-
-        IntegrationTestAbstract3 integTest =
-                scenarioExecution().getVar(IntegrationTestAbstract3.class.getName(), "current",
-                        IntegrationTestAbstract3.class);
-        integTest.tearDownAllModules();
-
-        after(sc);
-    }
 }
diff --git a/example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlue.java b/example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlueAbstract.java
similarity index 79%
copy from example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlue.java
copy to example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlueAbstract.java
index 29d13da..2ba0542 100644
--- a/example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlue.java
+++ b/example/application/simpleapp/application/src/test/java/domainapp/application/bdd/specglue/BootstrappingGlueAbstract.java
@@ -16,21 +16,29 @@ O *  Licensed to the Apache Software Foundation (ASF) under one or more
  */
 package domainapp.application.bdd.specglue;
 
+import org.apache.isis.applib.Module;
 import org.apache.isis.core.integtestsupport.IntegrationTestAbstract3;
 import org.apache.isis.core.specsupport.scenarios.ScenarioExecutionScope;
 import org.apache.isis.core.specsupport.specs.CukeGlueAbstract;
 
 import cucumber.api.java.After;
 import cucumber.api.java.Before;
-import domainapp.application.DomainAppApplicationModule;
 
-public class BootstrappingGlue extends CukeGlueAbstract {
+public abstract class BootstrappingGlueAbstract extends CukeGlueAbstract {
+
+    private final Module module;
+    private final Class[] additionalModuleClasses;
+
+    public BootstrappingGlueAbstract(final Module module, final Class... additionalModuleClasses) {
+        this.module = module;
+        this.additionalModuleClasses = additionalModuleClasses;
+    }
 
     @Before(value={"@integration"}, order=100)
     public void beforeScenarioIntegrationScope() {
 
         IntegrationTestAbstract3 integTest =
-                new IntegrationTestAbstract3(new DomainAppApplicationModule()) {};
+                new IntegrationTestAbstract3(module, additionalModuleClasses) {};
         integTest.bootstrapAndSetupIfRequired();
 
         before(ScenarioExecutionScope.INTEGRATION);

-- 
To stop receiving notification emails like this one, please contact
"commits@isis.apache.org" <co...@isis.apache.org>.