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/04/04 11:54:08 UTC

[isis] branch 2033-IoC_spring updated: ISIS-2033: makes 'SpecificationLoader' an interface

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

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


The following commit(s) were added to refs/heads/2033-IoC_spring by this push:
     new c9e9db0  ISIS-2033: makes 'SpecificationLoader' an interface
c9e9db0 is described below

commit c9e9db0b876e8184eee90e8f7662bae1923f1a9b
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Apr 4 13:53:58 2019 +0200

    ISIS-2033: makes 'SpecificationLoader' an interface
    
    - cleans up the SpecificationLoader implementation a bit
    - adds some initial command service infrastructure to 'incubator'
    
    Task-Url: https://issues.apache.org/jira/browse/ISIS-2033
---
 ...ervice.java => BackgroundExecutionService.java} |  15 +-
 ...ndService.java => CommandSchedulerService.java} |  14 +-
 .../isis/applib/services/command/Command.java      |  16 +-
 .../applib/services/command/CommandContext.java    |  11 +-
 .../applib/services/factory/FactoryService.java    |   9 +-
 .../metamodel/MetaModelServiceDefault.java         |   4 +-
 .../isis/core/metamodel/spec/ObjectSpecId.java     |  63 +-
 .../specloader/SpecificationCacheDefault.java      |   3 +-
 .../metamodel/specloader/SpecificationLoader.java  | 647 ++-------------------
 .../specloader/SpecificationLoaderDefault.java     | 606 +++++++++++++++++++
 .../specloader/specimpl/FacetedMethodsBuilder.java |  29 +-
 .../specloader/specimpl/IntrospectionState.java    |   2 +
 .../specimpl/ObjectSpecificationAbstract.java      |  48 +-
 .../traverser/SpecificationTraverser.java          |   4 +-
 .../isis/progmodels/dflt/JavaReflectorHelper.java  |   3 +-
 .../ActionAnnotationFacetFactoryTest_Command.java  |  23 +-
 ...ctionAnnotationFacetFactoryTest_Invocation.java |   8 +-
 ...ctionAnnotationFacetFactoryTest_Publishing.java |   6 +-
 .../SpecificationLoaderTestAbstract.java           |   2 +-
 .../background/BackgroundServiceDefault.java       |  14 +-
 .../background/CommandInvocationHandler.java       |   6 +-
 .../background/ForkingInvocationHandler.java       |   6 +-
 .../factory/FactoryServiceInternalDefault.java     |   6 -
 .../apache/isis/core/runtime/memento/Memento.java  |   3 +-
 .../isis/core/runtime/memento/StandaloneData.java  |   4 +-
 .../wicket/model/mementos/CollectionMemento.java   |   7 +-
 .../wicket/model/mementos/PropertyMemento.java     |   3 +-
 .../viewer/wicket/model/mementos/SpecUtils.java    |  10 +-
 .../src/main/java/isis/incubator/IsisBoot.java     |   1 +
 .../java/isis/incubator/IsisIncubatorModule.java   |   5 +
 .../command/CommandSchedulerServiceInMemory.java   |  23 +
 .../IncubatingBackgroundExecutionService.java      |  92 +++
 .../IncubatingCommandDtoServiceInternal.java       |  56 ++
 .../IncubatingCommandInvocationHandler.java        |  33 ++
 .../command/IncubatingFactoryService.java          |  21 +
 .../main/java/springapp/dom/customer/Customer.java |  27 +-
 .../springapp/dom/customer/CustomerRepository.java |   2 +
 .../src/main/java/springapp/dom/email/Email.java   |  49 ++
 .../java/springapp/dom/email/EmailRepository.java  |  11 +
 .../springapp/tests/command/CommandDemoBean.java   |  29 +
 .../java/springapp/tests/command/CommandTest.java  |  30 +
 .../java/springapp/tests/command/MessageBox.java   |  27 +
 42 files changed, 1219 insertions(+), 759 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundService.java b/core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundExecutionService.java
similarity index 81%
rename from core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundService.java
rename to core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundExecutionService.java
index f53f4b2..2e245e6 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundExecutionService.java
@@ -16,8 +16,6 @@
  */
 package org.apache.isis.applib.services.background;
 
-import org.apache.isis.applib.annotation.Programmatic;
-
 /**
  * Submit actions to be invoked in the background.
  *
@@ -26,32 +24,33 @@ import org.apache.isis.applib.annotation.Programmatic;
  * <pre>
  * public void submitInvoices() {
  *     for(Customer customer: customerRepository.findCustomersToInvoice()) {
- *         backgroundService.execute(customer).submitInvoice();
+ *         backgroundExecutionService.execute(customer).submitInvoice();
  *     }
  * }
  *
  * &#64;javax.inject.Inject
- * private BackgroundService backgroundService;
+ * private BackgroundExecutionService backgroundExecutionService;
  * </pre>
+ * 
+ * @since 2.0.0-M3 (renamed from 'BackgroundService')
+ * 
  */
-public interface BackgroundService {
+public interface BackgroundExecutionService {
 
     /**
      * Returns a proxy around the object (entity or view model) which is then used to obtain the
      * signature of the action to be invoked in the background.
      *
      * <p>
-     *     To obtain a proxy for a mixin, use {@link BackgroundService2#executeMixin(Class, Object)}.
+     *     To obtain a proxy for a mixin, use {@link BackgroundExecutionService#executeMixin(Class, Object)}.
      * </p>
      */
-    @Programmatic
     <T> T execute(final T object);
 
     /**
      * Returns a proxy around the mixin object which is then used to obtain the
      * signature of the action to be invoked in the background.
      */
-    @Programmatic
     <T> T executeMixin(Class<T> mixinClass, Object mixedIn);
 
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundCommandService.java b/core/applib/src/main/java/org/apache/isis/applib/services/background/CommandSchedulerService.java
similarity index 69%
rename from core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundCommandService.java
rename to core/applib/src/main/java/org/apache/isis/applib/services/background/CommandSchedulerService.java
index f91fa5a..5d06e36 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/background/BackgroundCommandService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/background/CommandSchedulerService.java
@@ -20,23 +20,19 @@ import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.schema.cmd.v1.CommandDto;
 
 /**
- * Persists a {@link org.apache.isis.schema.cmd.v1.CommandDto command-reified} action such that it can be executed asynchronously,
+ * Persists a {@link org.apache.isis.schema.cmd.v1.CommandDto command-reified} 
+ * action such that it can be executed asynchronously,
  * for example through a Quartz scheduler.
  *
  * <p>
- * Separate from {@link BackgroundService} primarily so that the default
+ * Separate from {@link BackgroundExecutionService} primarily so that the default
  * implementation, <tt>BackgroundServiceDefault</tt> (in <tt>isis-module-background</tt>) can
  * delegate to different implementations of this service.
  *
- * <p>
- * There is currently only implementation of this service, <tt>BackgroundCommandServiceJdo</tt> in
- * <tt>o.a.i.module:isis-module-command-jdo</tt>.  That implementation has no UI and no side-effects (the programmatic
- * API is through {@link org.apache.isis.applib.services.background.BackgroundService}).  It is therefore
- * annotated with {@link org.apache.isis.applib.annotation.DomainService} so that it is automatically registered as
- * a service.
+ * @since 2.0.0-M3 (renamed from 'BackgroundCommandService')
  *
  */
-public interface BackgroundCommandService {
+public interface CommandSchedulerService {
 
     public void schedule(
             final CommandDto dto,
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/command/Command.java b/core/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
index 717b4f5..064f3e3 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
@@ -26,8 +26,8 @@ import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.clock.Clock;
 import org.apache.isis.applib.events.domain.ActionDomainEvent;
 import org.apache.isis.applib.services.HasUniqueId;
-import org.apache.isis.applib.services.background.BackgroundCommandService;
-import org.apache.isis.applib.services.background.BackgroundService;
+import org.apache.isis.applib.services.background.CommandSchedulerService;
+import org.apache.isis.applib.services.background.BackgroundExecutionService;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.bookmark.BookmarkService;
 import org.apache.isis.applib.services.iactn.Interaction;
@@ -57,7 +57,7 @@ import org.apache.isis.schema.cmd.v1.CommandDto;
  *     This is done by {@link #next(String)}.  There are three possible sequences that might be generated:
  *     the sequence of changed domain objects being published by the {@link org.apache.isis.applib.services.publish.PublisherService#publish(Interaction.Execution)}; the
  *     sequence of wrapped action invocations (each being published), and finally one or more background commands
- *     that might be scheduled via the {@link BackgroundService}.
+ *     that might be scheduled via the {@link BackgroundExecutionService}.
  * </p>
  *
  */
@@ -133,7 +133,7 @@ public interface Command extends HasUniqueId {
     /**
      * The mechanism by which this command is to be executed, either synchronously &quot;in the
      * {@link CommandExecuteIn#FOREGROUND foreground}&quot; or is to be executed asynchronously &quot;in the
-     * {@link CommandExecuteIn#BACKGROUND background}&quot; through the {@link BackgroundCommandService}.
+     * {@link CommandExecuteIn#BACKGROUND background}&quot; through the {@link CommandSchedulerService}.
      */
     CommandExecuteIn getExecuteIn();
 
@@ -158,7 +158,7 @@ public interface Command extends HasUniqueId {
      * The (current) executor of this command.
      *
      * <p>
-     * Note that (even for implementations of {@link BackgroundCommandService} that persist {@link Command}s), this
+     * Note that (even for implementations of {@link CommandSchedulerService} that persist {@link Command}s), this
      * property is never (likely to be) persisted, because it is always updated to indicate how the command is
      * currently being executed.
      *
@@ -212,7 +212,7 @@ public interface Command extends HasUniqueId {
     // -- parent (property)
 
     /**
-     * For actions created through the {@link BackgroundService} and {@link BackgroundCommandService},
+     * For actions created through the {@link BackgroundExecutionService} and {@link CommandSchedulerService},
      * captures the parent action.
      */
     Command getParent();
@@ -253,7 +253,7 @@ public interface Command extends HasUniqueId {
     // -- persistence (property)
 
     /**
-     * Whether this command should ultimately be persisted (if the configured {@link BackgroundCommandService} supports
+     * Whether this command should ultimately be persisted (if the configured {@link CommandSchedulerService} supports
      * it) or not.
      *
      * <p>
@@ -270,7 +270,7 @@ public interface Command extends HasUniqueId {
      * on whether {@link #setPersistHint(boolean) a hint has been set} by some other means.
      *
      * <p>
-     * For example, a {@link BackgroundCommandService} implementation that creates persisted background commands ought
+     * For example, a {@link CommandSchedulerService} implementation that creates persisted background commands ought
      * associate them (via its {@link Command#getParent() parent}) to an original persisted
      * {@link Command}.  The hinting mechanism allows the service to suggest that the parent command be persisted so
      * that the app can then provide a mechanism to find all child background commands for that original parent command.
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandContext.java b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandContext.java
index 0b26c09..26e3405 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandContext.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandContext.java
@@ -16,13 +16,15 @@
  */
 package org.apache.isis.applib.services.command;
 
+import java.util.function.Supplier;
+
 import javax.enterprise.context.RequestScoped;
 
 /**
- * This service (API and implementation) provides access to context information about any {@link Command}.
+ * Holds the request scoped top level {@link Command}.
  */
 @RequestScoped
-public class CommandContext {
+public class CommandContext implements Supplier<Command> {
 
     private Command command;
 
@@ -37,4 +39,9 @@ public class CommandContext {
         this.command = command;
     }
 
+	@Override
+	public Command get() {
+		return getCommand();
+	}
+
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/factory/FactoryService.java b/core/applib/src/main/java/org/apache/isis/applib/services/factory/FactoryService.java
index 8bf5e3a..07d9cd1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/factory/FactoryService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/factory/FactoryService.java
@@ -19,7 +19,6 @@
 
 package org.apache.isis.applib.services.factory;
 
-import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.services.repository.RepositoryService;
 
 public interface FactoryService {
@@ -52,14 +51,12 @@ public interface FactoryService {
      * method.
      * </p>
      */
-    @Programmatic
     <T> T instantiate(Class<T> domainClass);
 
-
-    @Programmatic
     <T> T mixin(Class<T> mixinClass, Object mixedIn);
 
-    @Programmatic
-    <T> T m(Class<T> mixinClass, Object mixedIn);
+    default <T> T m(Class<T> mixinClass, Object mixedIn) {
+    	return mixin(mixinClass, mixedIn);
+    }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
index 87ead6a..1b2b0c5 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
@@ -87,11 +87,11 @@ public class MetaModelServiceDefault implements MetaModelService {
 
     @Override
     public void rebuild(final Class<?> domainType) {
-        specificationLoader.invalidateCache(domainType);
         
         GridService gridService = _CDI.getSingletonElseFail(GridService.class);
         gridService.remove(domainType);
-        specificationLoader.loadSpecification(domainType);
+        
+        specificationLoader.reloadSpecification(domainType);
     }
 
     // //////////////////////////////////////
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecId.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecId.java
index 9a7f3a8..7fa62d4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecId.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecId.java
@@ -18,13 +18,13 @@
  */
 package org.apache.isis.core.metamodel.spec;
 
-import static org.apache.isis.commons.internal.base._With.requiresNotEmpty;
-
 import java.io.Serializable;
-import java.util.Objects;
 
 import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
 
+import lombok.NonNull;
+import lombok.Value;
+
 /**
  * Represents an {@link ObjectSpecification}, as determined by
  * an {@link ObjectSpecIdFacet}.
@@ -32,44 +32,45 @@ import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFac
  * <p>
  * Has value semantics.
  */
+@Value(staticConstructor = "of")
 public final class ObjectSpecId implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
-    private final String specId;
+    @NonNull private final String specId;
 
-    public static ObjectSpecId of(String specId) {
-        requiresNotEmpty(specId, "specId");
-        return new ObjectSpecId(specId);
-    }
-
-    private ObjectSpecId(String specId) {
-        this.specId = specId;
-    }
+//    public static ObjectSpecId of(String specId) {
+//        requiresNotEmpty(specId, "specId");
+//        return new ObjectSpecId(specId);
+//    }
+//
+//    private ObjectSpecId(String specId) {
+//        this.specId = specId;
+//    }
 
     public String asString() {
         return specId;
     }
 
-    @Override
-    public int hashCode() {
-        return specId.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final ObjectSpecId other = (ObjectSpecId) obj;
-        return Objects.equals(specId, other.specId);
-    }
+//    @Override
+//    public int hashCode() {
+//        return specId.hashCode();
+//    }
+//
+//    @Override
+//    public boolean equals(Object obj) {
+//        if (this == obj) {
+//            return true;
+//        }
+//        if (obj == null) {
+//            return false;
+//        }
+//        if (getClass() != obj.getClass()) {
+//            return false;
+//        }
+//        final ObjectSpecId other = (ObjectSpecId) obj;
+//        return Objects.equals(specId, other.specId);
+//    }
 
     @Override
     public String toString() {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationCacheDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationCacheDefault.java
index d7d044e..bf3b6d3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationCacheDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationCacheDefault.java
@@ -38,7 +38,8 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
  * This allows {@link #allSpecifications()} to return a list of specs.
  * Later on, {@link #init()} called which populates #classNameBySpecId.
  *
- * Attempting to call {@link #getByObjectType(ObjectSpecId)} before {@link #init() initialisation} will result in an
+ * Attempting to call {@link #getByObjectType(ObjectSpecId)} before 
+ * {@link #init() initialisation} will result in an
  * {@link IllegalStateException}.
  *
  */
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoader.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoader.java
index 9c5a41d..a12e912 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoader.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoader.java
@@ -1,4 +1,4 @@
-/**
+/*
  *  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.
@@ -16,570 +16,38 @@
  */
 package org.apache.isis.core.metamodel.specloader;
 
-import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Future;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
-import javax.enterprise.inject.Vetoed;
+import javax.annotation.Nullable;
 
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.annotation.NatureOfService;
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.services.inject.ServiceInjector;
-import org.apache.isis.applib.services.registry.ServiceRegistry;
-import org.apache.isis.applib.services.registry.ServiceRegistry.BeanAdapter;
-import org.apache.isis.commons.internal.base._NullSafe;
-import org.apache.isis.commons.internal.collections._Lists;
-import org.apache.isis.commons.internal.context._Context;
-import org.apache.isis.commons.internal.debug._Probe;
-import org.apache.isis.config.IsisConfiguration;
-import org.apache.isis.config.internal._Config;
 import org.apache.isis.config.property.ConfigPropertyBoolean;
 import org.apache.isis.config.property.ConfigPropertyEnum;
-import org.apache.isis.config.registry.IsisBeanTypeRegistry;
-import org.apache.isis.core.commons.ensure.Assert;
-import org.apache.isis.core.commons.exceptions.IsisException;
-import org.apache.isis.core.commons.lang.ClassUtil;
-import org.apache.isis.core.metamodel.MetaModelContext;
-import org.apache.isis.core.metamodel.facetapi.Facet;
-import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
-import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
-import org.apache.isis.core.metamodel.spec.FreeStandingList;
 import org.apache.isis.core.metamodel.spec.ObjectSpecId;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
-import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor;
-import org.apache.isis.core.metamodel.specloader.postprocessor.PostProcessor;
-import org.apache.isis.core.metamodel.specloader.specimpl.FacetedMethodsBuilderContext;
 import org.apache.isis.core.metamodel.specloader.specimpl.IntrospectionState;
-import org.apache.isis.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract;
-import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
-import org.apache.isis.core.metamodel.specloader.specimpl.standalonelist.ObjectSpecificationOnStandaloneList;
-import org.apache.isis.core.metamodel.specloader.validator.MetaModelDeficiencies;
-import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidator;
-import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
-import org.apache.isis.core.runtime.threadpool.ThreadPoolExecutionMode;
-import org.apache.isis.core.runtime.threadpool.ThreadPoolSupport;
-import org.apache.isis.progmodels.dflt.ProgrammingModelFacetsJava5;
-import org.apache.isis.schema.utils.CommonDtoUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import lombok.val;
 
 /**
  * Builds the meta-model.
- *
- * <p>
- * The implementation provides for a degree of pluggability:
- * <ul>
- * <li>The most important plug-in point is {@link ProgrammingModel} that
- * specifies the set of {@link Facet} that make up programming model. If not
- * specified then defaults to {@link ProgrammingModelFacetsJava5} (which should
- * be used as a starting point for your own customizations).
- * <li>The only mandatory plug-in point is {@link ClassSubstitutor}, which
- * allows the class to be loaded to be substituted if required. This is used in
- * conjunction with some <tt>PersistenceMechanism</tt>s that do class
- * enhancement.
- * </ul>
- * </p>
- *
- * <p>
- * Implementing class is added to {@link ServiceInjector} as an (internal) domain service; all public methods
- * must be annotated using {@link Programmatic}.
- * </p>
+ * TODO [2033] add missing java-doc
  *
  */
-@Vetoed // has a producer
-public class SpecificationLoader {
-
-    private final static Logger LOG = LoggerFactory.getLogger(SpecificationLoader.class);
+public interface SpecificationLoader {
 
-    // -- constructor, fields
-    public static final ConfigPropertyBoolean CONFIG_PROPERTY_PARALLELIZE =
+    static final ConfigPropertyBoolean CONFIG_PROPERTY_PARALLELIZE =
             new ConfigPropertyBoolean("isis.reflector.introspector.parallelize", true);
 
-    public static final ConfigPropertyEnum<IntrospectionMode> CONFIG_PROPERTY_MODE =
+    static final ConfigPropertyEnum<IntrospectionMode> CONFIG_PROPERTY_MODE =
             new ConfigPropertyEnum<>("isis.reflector.introspector.mode", IntrospectionMode.LAZY_UNLESS_PRODUCTION);
 
-
-    private final ClassSubstitutor classSubstitutor = new ClassSubstitutor();
-
-    private final ProgrammingModel programmingModel;
-    private final FacetProcessor facetProcessor;
-
-
-    private final MetaModelValidator metaModelValidator;
-    private final SpecificationCacheDefault cache = new SpecificationCacheDefault();
-    private final PostProcessor postProcessor;
-
-
-    public SpecificationLoader(
-            final ProgrammingModel programmingModel,
-            final MetaModelValidator metaModelValidator) {
-
-        this.programmingModel = programmingModel;
-        this.metaModelValidator = metaModelValidator;
-
-        this.facetProcessor = new FacetProcessor(programmingModel);
-        this.postProcessor = new PostProcessor(programmingModel);
-    }
-
-
-    // -- init
-
-
-    /**
-     * Initializes and wires up, and primes the cache based on any service
-     * classes (provided by the {@link ServicesInjector}).
-     */
-    public void init() {
-
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("initialising {}", this);
-        }
-
-        // wire subcomponents into each other
-        //facetProcessor.setServicesInjector(servicesInjector);
-
-        // initialize subcomponents
-        this.programmingModel.init();
-        facetProcessor.init();
-
-        postProcessor.init();
-        metaModelValidator.init();
-
-
-        // need to completely load services and mixins (synchronously)
-        LOG.info("Loading all specs (up to state of {})", IntrospectionState.NOT_INTROSPECTED);
-
-        val typeRegistry = IsisBeanTypeRegistry.current(); 
-        
-        final List<ObjectSpecification> specificationsFromRegistry = _Lists.newArrayList();
-
-        // we use allServiceClasses() - obtained from servicesInjector - rather than reading from the
-        // AppManifest.Registry.instance().getDomainServiceTypes(), because the former also has the fallback
-        // services set up in IsisSessionFactoryBuilder beforehand.
-        final List<ObjectSpecification> domainServiceSpecs =
-        loadSpecificationsForBeans(
-                streamBeans(), NatureOfService.DOMAIN,
-                specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
-        );
-        final List<ObjectSpecification> mixinSpecs =
-        loadSpecificationsFor(
-        		typeRegistry.getMixinTypes().stream(), null,
-                specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
-        );
-        loadSpecificationsFor(
-                CommonDtoUtils.VALUE_TYPES.stream(), null,
-                specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
-        );
-        loadSpecificationsFor(
-        		typeRegistry.getDomainObjectTypes().stream(), null,
-                specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
-        );
-        loadSpecificationsFor(
-        		typeRegistry.getViewModelTypes().stream(), null,
-                specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
-        );
-        loadSpecificationsFor(
-        		typeRegistry.getXmlElementTypes().stream(), null,
-                specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
-        );
-
-        cache.init();
-
-        final Collection<ObjectSpecification> cachedSpecifications = allCachedSpecifications();
-
-        logBefore(specificationsFromRegistry, cachedSpecifications);
-
-        LOG.info("Introspecting all specs up to {}", IntrospectionState.TYPE_INTROSPECTED);
-        introspect(specificationsFromRegistry, IntrospectionState.TYPE_INTROSPECTED);
-
-        LOG.info("Introspecting domainService specs up to {}", IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
-        introspect(domainServiceSpecs, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
-
-        LOG.info("Introspecting mixin specs up to {}", IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
-        introspect(mixinSpecs, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
-
-        logAfter(cachedSpecifications);
-
-        final IntrospectionMode mode = CONFIG_PROPERTY_MODE.from(getConfiguration());
-        if(mode.isFullIntrospect(_Context.getEnvironment().getDeploymentType())) {
-            LOG.info("Introspecting all cached specs up to {}", IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
-            introspect(cachedSpecifications, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
-        }
-
-        LOG.info("init() - done");
-        
-        //FIXME [2033] remove debug code ...
-        //{
-//        	streamServiceClasses()
-//        	.forEach(service->probe.println("using service %s", service));
-//        	
-//        	
-//        	val metaModelService = _CDI.getSingleton(MetaModelService.class);
-//        	val jaxbService = _CDI.getSingleton(JaxbService.class);
-//        	
-//        	val metamodelDto =
-//        			metaModelService.exportMetaModel(
-//        					new MetaModelService.Config()
-//        							.withIgnoreNoop()
-//        							.withIgnoreAbstractClasses()
-//        							.withIgnoreBuiltInValueTypes()
-//        							.withIgnoreInterfaces()
-//        							.withPackagePrefix("domainapp")
-//        			);
-//			
-//			final String xml = jaxbService.toXml(metamodelDto);
-//			//probe.println(xml);
-//        }
-    }
+    // -- LIVE CYCLE 
     
-    private final static _Probe probe = _Probe.unlimited().label("SpecificationLoader");
-
-    private void logBefore(
-            final List<ObjectSpecification> specificationsFromRegistry,
-            final Collection<ObjectSpecification> cachedSpecifications) {
-        if(!LOG.isDebugEnabled()) {
-            return;
-        }
-        LOG.debug(String.format(
-                "specificationsFromRegistry.size = %d ; cachedSpecifications.size = %d",
-                specificationsFromRegistry.size(), cachedSpecifications.size()));
-
-        List<ObjectSpecification> registryNotCached = specificationsFromRegistry.stream()
-                .filter(spec -> !cachedSpecifications.contains(spec))
-                .collect(Collectors.toList());
-        List<ObjectSpecification> cachedNotRegistry = cachedSpecifications.stream()
-                .filter(spec -> !specificationsFromRegistry.contains(spec))
-                .collect(Collectors.toList());
-
-        LOG.debug(String.format(
-                "registryNotCached.size = %d ; cachedNotRegistry.size = %d",
-                registryNotCached.size(), cachedNotRegistry.size()));
-    }
-
-    private void logAfter(final Collection<ObjectSpecification> cachedSpecifications) {
-        if(!LOG.isDebugEnabled()) {
-            return;
-        }
-
-        final Collection<ObjectSpecification> cachedSpecificationsAfter = cache.allSpecifications();
-        List<ObjectSpecification> cachedAfterNotBefore = cachedSpecificationsAfter.stream()
-                .filter(spec -> !cachedSpecifications.contains(spec))
-                .collect(Collectors.toList());
-        LOG.debug(String.format("cachedSpecificationsAfter.size = %d ; cachedAfterNotBefore.size = %d",
-                cachedSpecificationsAfter.size(), cachedAfterNotBefore.size()));
-    }
-
-    private void introspect(final Collection<ObjectSpecification> specs, final IntrospectionState upTo) {
-        final List<Callable<Object>> callables = _Lists.newArrayList();
-        for (final ObjectSpecification specification : specs) {
-            Callable<Object> callable = new Callable<Object>() {
-                @Override
-                public Object call() {
-
-                    final ObjectSpecificationAbstract specSpi = (ObjectSpecificationAbstract) specification;
-                    specSpi.introspectUpTo(upTo);
-
-                    return null;
-                }
-                public String toString() {
-                    return String.format(
-                            "%s: #introspectUpTo( %s )",
-                            specification.getFullIdentifier(), upTo);
-                }
-            };
-            callables.add(callable);
-        }
-        
-        invokeAndWait(callables);
-        }
-
-    private void invokeAndWait(final List<Callable<Object>> callables) {
-        final ThreadPoolSupport threadPoolSupport = ThreadPoolSupport.getInstance();
-        final boolean parallelize = CONFIG_PROPERTY_PARALLELIZE.from(getConfiguration());
-        
-        final ThreadPoolExecutionMode executionModeFromConfig = parallelize
-                ? ThreadPoolExecutionMode.PARALLEL
-                        : ThreadPoolExecutionMode.SEQUENTIAL;
-        
-        final List<Future<Object>> futures = 
-                threadPoolSupport.invokeAll(executionModeFromConfig, callables);
-        threadPoolSupport.joinGatherFailures(futures);
-    }
-
-    private List<ObjectSpecification> loadSpecificationsFor(
-            final Stream<Class<?>> domainTypes,
-            final NatureOfService natureOfServiceFallback,
-            final List<ObjectSpecification> appendTo,
-            final IntrospectionState upTo) {
-
-        return domainTypes
-        .map(domainType->internalLoadSpecification(domainType, natureOfServiceFallback, upTo))
-        .filter(_NullSafe::isPresent)
-        .peek(appendTo::add)
-        .collect(Collectors.toList());
-    }
-
-    private List<ObjectSpecification> loadSpecificationsForBeans (
-            final Stream<BeanAdapter> beans,
-            final NatureOfService natureOfServiceFallback,
-            final List<ObjectSpecification> appendTo,
-            final IntrospectionState upTo) {
-
-        return beans
-        .filter(bean->bean.isDomainService())
-        .map(bean->bean.getBean().getBeanClass())    
-        .map(domainType->internalLoadSpecification(domainType, natureOfServiceFallback, upTo))
-        .filter(_NullSafe::isPresent)
-        .peek(appendTo::add)
-        .collect(Collectors.toList());
-    }
-
-    // -- shutdown
-
-    public void shutdown() {
-        LOG.info("shutting down {}", this);
-
-        cache.clear();
-    }
-
-    // -- invalidateCache
-
-    public void invalidateCache(final Class<?> cls) {
-
-        if(!cache.isInitialized()) {
-            // could be called by JRebel plugin, before we are up-and-running
-            // just ignore.
-            return;
-        }
-        final Class<?> substitutedType = classSubstitutor.getClass(cls);
-
-        if(substitutedType.isAnonymousClass()) {
-            // JRebel plugin might call us... just ignore 'em.
-            return;
-        }
-
-        ObjectSpecification spec = loadSpecification(substitutedType, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
-        while(spec != null) {
-            final Class<?> type = spec.getCorrespondingClass();
-            cache.remove(type.getName());
-            if(spec.containsDoOpFacet(ObjectSpecIdFacet.class)) {
-                // umm.  Some specs do not have an ObjectSpecIdFacet...
-                recache(spec);
-            }
-            spec = spec.superclass();
-        }
-    }
-
-
-    private void recache(final ObjectSpecification newSpec) {
-        cache.recache(newSpec);
-    }
-
-    // -- validation
-
-    private ValidationFailures validationFailures;
-
-    public MetaModelDeficiencies validateThenGetDeficienciesIfAny() {
-        final IntrospectionMode mode = CONFIG_PROPERTY_MODE.from(getConfiguration());
-        if(!mode.isFullIntrospect(_Context.getEnvironment().getDeploymentType())) {
-            LOG.info("Meta model validation skipped (full introspection of metamodel not configured)");
-            return null;
-        }
-
-        ValidationFailures validationFailures = validate();
-        return validationFailures.getDeficienciesIfAny();
-    }
-
-    public ValidationFailures validate() {
-        if(validationFailures == null) {
-            validationFailures = new ValidationFailures();
-            metaModelValidator.validate(validationFailures);
-        }
-        return validationFailures;
-    }
-
-    // -- loadSpecification, loadSpecifications
-
-    /**
-     * Return the specification for the specified class of object.
-     *
-     * <p>
-     * It is possible for this method to return <tt>null</tt>, for example if
-     * the configured {@link org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor}
-     * has filtered out the class.
-     */
-    public ObjectSpecification loadSpecification(final String className) {
-        return loadSpecification(className, IntrospectionState.TYPE_INTROSPECTED);
-    }
-
-    public ObjectSpecification loadSpecification(final String className, final IntrospectionState upTo) {
-        assert className != null;
-
-        try {
-            final Class<?> cls = loadBuiltIn(className);
-            return internalLoadSpecification(cls, null, upTo);
-        } catch (final ClassNotFoundException e) {
-            final ObjectSpecification spec = cache.get(className);
-            if (spec == null) {
-                throw new IsisException("No such class available: " + className);
-            }
-            return spec;
-        }
-    }
-
-    public ObjectSpecification loadSpecification(final Class<?> type) {
-        return loadSpecification(type, IntrospectionState.TYPE_INTROSPECTED);
-    }
-
-    @Programmatic
-    public ObjectSpecification peekSpecification(final Class<?> type) {
-
-        final Class<?> substitutedType = classSubstitutor.getClass(type);
-        if (substitutedType == null) {
-            return null;
-        }
-
-        final String typeName = substitutedType.getName();
-        ObjectSpecification spec = cache.get(typeName);
-        if (spec != null) {
-            return spec;
-        }
-
-        return null;
-    }
-
-    public ObjectSpecification loadSpecification(final Class<?> type, final IntrospectionState upTo) {
-        final ObjectSpecification spec = internalLoadSpecification(type, null, upTo);
-        if(spec == null) {
-            return null;
-        }
-
-        // TODO: review, is this now needed?
-        //  We now create the ObjectSpecIdFacet immediately after creating the ObjectSpecification,
-        //  so the cache shouldn't need updating here also.
-        if(cache.isInitialized()) {
-            // umm.  It turns out that anonymous inner classes (eg org.estatio.dom.WithTitleGetter$ToString$1)
-            // don't have an ObjectSpecId; hence the guard.
-            if(spec.containsDoOpFacet(ObjectSpecIdFacet.class)) {
-                ObjectSpecId specId = spec.getSpecId();
-                if (cache.getByObjectType(specId) == null) {
-                    cache.recache(spec);
-                }
-            }
-        }
-        return spec;
-    }
-
-    private ObjectSpecification internalLoadSpecification(
-            final Class<?> type,
-            final NatureOfService natureFallback,
-            final IntrospectionState upTo) {
-
-        final Class<?> substitutedType = classSubstitutor.getClass(type);
-        if (substitutedType == null) {
-            return null;
-    }
-        Assert.assertNotNull(substitutedType);
-
-        final String typeName = substitutedType.getName();
-        ObjectSpecification spec = cache.get(typeName);
-        if (spec != null) {
-            return spec;
-        }
-
-        synchronized (this) {
-            // inside the synchronized block
-            spec = cache.get(typeName);
-        if (spec != null) {
-            return spec;
-        }
-
-            final ObjectSpecification specification = createSpecification(substitutedType, natureFallback);
-
-        // put into the cache prior to introspecting, to prevent
-        // infinite loops
-        cache.cache(typeName, specification);
-
-            final ObjectSpecificationAbstract specSpi = (ObjectSpecificationAbstract) specification;
-            specSpi.introspectUpTo(upTo);
-
-        return specification;
-    }
-    }
-
-    /**
-     * Loads the specifications of the specified types except the one specified
-     * (to prevent an infinite loop).
-     */
-    public boolean loadSpecifications(
-            final List<Class<?>> typesToLoad,
-            final Class<?> typeToIgnore,
-            final IntrospectionState upTo) {
-        boolean anyLoadedAsNull = false;
-        for (final Class<?> typeToLoad : typesToLoad) {
-            if (typeToLoad != typeToIgnore) {
-                final ObjectSpecification objectSpecification =
-                        internalLoadSpecification(typeToLoad, null, upTo);
-                final boolean loadedAsNull = (objectSpecification == null);
-                anyLoadedAsNull = loadedAsNull || anyLoadedAsNull;
-            }
-        }
-        return anyLoadedAsNull;
-    }
-
-    /**
-     * Creates the appropriate type of {@link ObjectSpecification}.
-     */
-    private ObjectSpecification createSpecification(
-            final Class<?> cls,
-            final NatureOfService fallback) {
-
-        // ... and create the specs
-        final ObjectSpecificationAbstract objectSpec;
-        if (FreeStandingList.class.isAssignableFrom(cls)) {
-
-            objectSpec = new ObjectSpecificationOnStandaloneList(facetProcessor, postProcessor);
-
-        } else {
-
-            final FacetedMethodsBuilderContext facetedMethodsBuilderContext =
-                    new FacetedMethodsBuilderContext(
-                            this, facetProcessor);
-
-            final NatureOfService natureOfServiceIfAny = natureOfServiceFrom(cls, fallback);
-
-            objectSpec = new ObjectSpecificationDefault(cls,
-                                    facetedMethodsBuilderContext,
-                                    facetProcessor, natureOfServiceIfAny, postProcessor);
-        }
-
-        return objectSpec;
-    }
-
-    private NatureOfService natureOfServiceFrom(
-            final Class<?> type,
-            final NatureOfService fallback) {
-        final DomainService domainServiceIfAny = type.getAnnotation(DomainService.class);
-        return domainServiceIfAny != null ? domainServiceIfAny.nature() : fallback;
-    }
-
-    private Class<?> loadBuiltIn(final String className) throws ClassNotFoundException {
-        final Class<?> builtIn = ClassUtil.getBuiltIn(className);
-        if (builtIn != null) {
-            return builtIn;
-        }
-        return ClassUtil.forName(className);
-    }
-
-    // -- allSpecifications
-    /**
+    void init();
+    
+    void shutdown();
+    
+    // -- LOOKUP
+    
+	/**
      * Returns (a new list holding a copy of) all the loaded specifications.
      *
      * <p>
@@ -588,49 +56,48 @@ public class SpecificationLoader {
      *     ObjectSpec's being discovered, eg performing metamodel validation.
      * </p>
      */
-    public List<ObjectSpecification> allSpecifications() {
-        return _Lists.newArrayList(allCachedSpecifications());
-    }
-
-    private Collection<ObjectSpecification> allCachedSpecifications() {
-        return cache.allSpecifications();
-    }
-
-    private Stream<BeanAdapter> streamBeans() {
-        final ServiceRegistry registry = MetaModelContext.current().getServiceRegistry();
-        return registry.streamRegisteredBeans();
-    }
-
-    // -- loaded
-    /**
-     * Whether this class has been loaded.
-     */
-    public boolean loaded(final Class<?> cls) {
-        return loaded(cls.getName());
-    }
-
-    /**
-     * @see #loaded(Class).
-     */
-    public boolean loaded(final String fullyQualifiedClassName) {
-        return cache.get(fullyQualifiedClassName) != null;
-    }
-
-    // -- lookupBySpecId
-    public ObjectSpecification lookupBySpecId(ObjectSpecId objectSpecId) {
-        if(!cache.isInitialized()) {
-            throw new IllegalStateException("Internal cache not yet initialized");
-        }
-        final ObjectSpecification objectSpecification = cache.getByObjectType(objectSpecId);
-        if(objectSpecification == null) {
-            // fallback
-            return loadSpecification(objectSpecId.asString(), IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
-        }
-        return objectSpecification;
-    }
-
-    public IsisConfiguration getConfiguration() {
-        return _Config.getConfiguration();
-    }
+	List<ObjectSpecification> allSpecifications();
+
+	ObjectSpecification lookupBySpecId(ObjectSpecId objectSpecId);
+	
+    // -- LOADING
+
+	void reloadSpecification(Class<?> domainType);
+
+	/**
+	 * Return the specification for the specified class of object.
+	 *
+	 * <p>
+	 * It is possible for this method to return <tt>null</tt>, for example if
+	 * the configured {@link org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor}
+	 * has filtered out the class.
+	 * 
+	 * @return {@code null} if {@code domainType==null}
+	 */
+	ObjectSpecification loadSpecification(@Nullable Class<?> domainType, IntrospectionState upTo);
+	
+	/**
+	 * Return the specification for the specified ObjectSpecId.
+	 *
+	 * <p>
+	 * It is possible for this method to return <tt>null</tt>, for example if
+	 * the configured {@link org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor}
+	 * has filtered out the class.
+	 * 
+	 * @return {@code null} if {@code objectSpecId==null} 
+	 */
+	ObjectSpecification loadSpecification(@Nullable ObjectSpecId objectSpecId, IntrospectionState upTo);
+	
+	// -- SHORTCUTS
+	
+	default ObjectSpecification loadSpecification(@Nullable final Class<?> domainType) {
+        return loadSpecification(domainType, IntrospectionState.TYPE_INTROSPECTED);
+    }
+ 
+	default ObjectSpecification loadSpecification(@Nullable ObjectSpecId objectSpecId) {
+		return loadSpecification(objectSpecId, IntrospectionState.TYPE_INTROSPECTED);
+    }
+
+	
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderDefault.java
new file mode 100644
index 0000000..59781c2
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderDefault.java
@@ -0,0 +1,606 @@
+package org.apache.isis.core.metamodel.specloader;
+
+import static org.apache.isis.commons.internal.base._With.requires;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
+import javax.enterprise.inject.Vetoed;
+
+import org.apache.isis.applib.annotation.DomainService;
+import org.apache.isis.applib.annotation.NatureOfService;
+import org.apache.isis.applib.services.registry.ServiceRegistry;
+import org.apache.isis.applib.services.registry.ServiceRegistry.BeanAdapter;
+import org.apache.isis.commons.internal.base._NullSafe;
+import org.apache.isis.commons.internal.base._Strings;
+import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.context._Context;
+import org.apache.isis.commons.internal.debug._Probe;
+import org.apache.isis.config.IsisConfiguration;
+import org.apache.isis.config.internal._Config;
+import org.apache.isis.config.registry.IsisBeanTypeRegistry;
+import org.apache.isis.core.commons.exceptions.IsisException;
+import org.apache.isis.core.commons.lang.ClassUtil;
+import org.apache.isis.core.metamodel.MetaModelContext;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
+import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
+import org.apache.isis.core.metamodel.spec.FreeStandingList;
+import org.apache.isis.core.metamodel.spec.ObjectSpecId;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
+import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor;
+import org.apache.isis.core.metamodel.specloader.postprocessor.PostProcessor;
+import org.apache.isis.core.metamodel.specloader.specimpl.FacetedMethodsBuilderContext;
+import org.apache.isis.core.metamodel.specloader.specimpl.IntrospectionState;
+import org.apache.isis.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract;
+import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
+import org.apache.isis.core.metamodel.specloader.specimpl.standalonelist.ObjectSpecificationOnStandaloneList;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelDeficiencies;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidator;
+import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
+import org.apache.isis.core.runtime.threadpool.ThreadPoolExecutionMode;
+import org.apache.isis.core.runtime.threadpool.ThreadPoolSupport;
+import org.apache.isis.progmodels.dflt.ProgrammingModelFacetsJava5;
+import org.apache.isis.schema.utils.CommonDtoUtils;
+
+import lombok.val;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * <p>
+ * The implementation provides for a degree of pluggability:
+ * <ul>
+ * <li>The most important plug-in point is {@link ProgrammingModel} that
+ * specifies the set of {@link Facet} that make up programming model. If not
+ * specified then defaults to {@link ProgrammingModelFacetsJava5} (which should
+ * be used as a starting point for your own customizations).
+ * <li>The only mandatory plug-in point is {@link ClassSubstitutor}, which
+ * allows the class to be loaded to be substituted if required. This is used in
+ * conjunction with some <tt>PersistenceMechanism</tt>s that do class
+ * enhancement.
+ * </ul>
+ * </p>
+ */
+@Vetoed // has a producer
+@Slf4j
+public class SpecificationLoaderDefault implements SpecificationLoader {
+
+	private final ClassSubstitutor classSubstitutor = new ClassSubstitutor();
+
+	private final ProgrammingModel programmingModel;
+	private final FacetProcessor facetProcessor;
+
+
+	private final MetaModelValidator metaModelValidator;
+	private final SpecificationCacheDefault cache = new SpecificationCacheDefault();
+	private final PostProcessor postProcessor;
+
+
+	public SpecificationLoaderDefault(
+			final ProgrammingModel programmingModel,
+			final MetaModelValidator metaModelValidator) {
+
+		this.programmingModel = programmingModel;
+		this.metaModelValidator = metaModelValidator;
+
+		this.facetProcessor = new FacetProcessor(programmingModel);
+		this.postProcessor = new PostProcessor(programmingModel);
+	}
+
+	// -- LIVE CYCLE
+
+	/**
+	 * Initializes and wires up, and primes the cache based on any service
+	 * classes (provided by the {@link ServicesInjector}).
+	 */
+	@Override
+	public void init() {
+
+		if (log.isDebugEnabled()) {
+			log.debug("initialising {}", this);
+		}
+
+		// wire subcomponents into each other
+		//facetProcessor.setServicesInjector(servicesInjector);
+
+		// initialize subcomponents
+		this.programmingModel.init();
+		facetProcessor.init();
+
+		postProcessor.init();
+		metaModelValidator.init();
+
+
+		// need to completely load services and mixins (synchronously)
+		log.info("Loading all specs (up to state of {})", IntrospectionState.NOT_INTROSPECTED);
+
+		val typeRegistry = IsisBeanTypeRegistry.current(); 
+
+		final List<ObjectSpecification> specificationsFromRegistry = _Lists.newArrayList();
+
+		// we use allServiceClasses() - obtained from servicesInjector - rather than reading from the
+		// AppManifest.Registry.instance().getDomainServiceTypes(), because the former also has the fallback
+		// services set up in IsisSessionFactoryBuilder beforehand.
+		final List<ObjectSpecification> domainServiceSpecs =
+				loadSpecificationsForBeans(
+						streamBeans(), NatureOfService.DOMAIN,
+						specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
+						);
+		final List<ObjectSpecification> mixinSpecs =
+				loadSpecificationsFor(
+						typeRegistry.getMixinTypes().stream(), null,
+						specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
+						);
+		loadSpecificationsFor(
+				CommonDtoUtils.VALUE_TYPES.stream(), null,
+				specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
+				);
+		loadSpecificationsFor(
+				typeRegistry.getDomainObjectTypes().stream(), null,
+				specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
+				);
+		loadSpecificationsFor(
+				typeRegistry.getViewModelTypes().stream(), null,
+				specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
+				);
+		loadSpecificationsFor(
+				typeRegistry.getXmlElementTypes().stream(), null,
+				specificationsFromRegistry, IntrospectionState.NOT_INTROSPECTED
+				);
+
+		cache.init();
+
+		final Collection<ObjectSpecification> cachedSpecifications = allCachedSpecifications();
+
+		logBefore(specificationsFromRegistry, cachedSpecifications);
+
+		log.info("Introspecting all specs up to {}", IntrospectionState.TYPE_INTROSPECTED);
+		introspect(specificationsFromRegistry, IntrospectionState.TYPE_INTROSPECTED);
+
+		log.info("Introspecting domainService specs up to {}", IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
+		introspect(domainServiceSpecs, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
+
+		log.info("Introspecting mixin specs up to {}", IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
+		introspect(mixinSpecs, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
+
+		logAfter(cachedSpecifications);
+
+		final IntrospectionMode mode = CONFIG_PROPERTY_MODE.from(getConfiguration());
+		if(mode.isFullIntrospect(_Context.getEnvironment().getDeploymentType())) {
+			log.info("Introspecting all cached specs up to {}", IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
+			introspect(cachedSpecifications, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
+		}
+
+		log.info("init() - done");
+
+		//FIXME [2033] remove debug code ...
+		//{
+		//        	streamServiceClasses()
+		//        	.forEach(service->probe.println("using service %s", service));
+		//        	
+		//        	
+		//        	val metaModelService = _CDI.getSingleton(MetaModelService.class);
+		//        	val jaxbService = _CDI.getSingleton(JaxbService.class);
+		//        	
+		//        	val metamodelDto =
+		//        			metaModelService.exportMetaModel(
+		//        					new MetaModelService.Config()
+		//        							.withIgnoreNoop()
+		//        							.withIgnoreAbstractClasses()
+		//        							.withIgnoreBuiltInValueTypes()
+		//        							.withIgnoreInterfaces()
+		//        							.withPackagePrefix("domainapp")
+		//        			);
+		//			
+		//			final String xml = jaxbService.toXml(metamodelDto);
+		//			//probe.println(xml);
+		//        }
+	}
+
+	@Override
+	public void shutdown() {
+		cache.clear();
+		
+		log.info("shutting down {}", this);
+	}
+
+	// -- VALIDATION
+
+	private ValidationFailures validationFailures;
+
+	private MetaModelDeficiencies validateThenGetDeficienciesIfAny() {
+		final IntrospectionMode mode = CONFIG_PROPERTY_MODE.from(getConfiguration());
+		if(!mode.isFullIntrospect(_Context.getEnvironment().getDeploymentType())) {
+			log.info("Meta model validation skipped (full introspection of metamodel not configured)");
+			return null;
+		}
+
+		ValidationFailures validationFailures = validate();
+		return validationFailures.getDeficienciesIfAny();
+	}
+
+	private ValidationFailures validate() {
+		if(validationFailures == null) {
+			validationFailures = new ValidationFailures();
+			metaModelValidator.validate(validationFailures);
+		}
+		return validationFailures;
+	}
+
+	// -- SPEC LOADING
+	
+	@Override
+	public void reloadSpecification(Class<?> domainType) {
+		invalidateCache(domainType);
+		loadSpecification(domainType);
+	}
+
+	@Override
+	public ObjectSpecification loadSpecification(
+			@Nullable final ObjectSpecId objectSpecId, 
+			final IntrospectionState upTo) {
+		
+		if(objectSpecId==null) {
+			return null;
+		}
+		
+		val className = objectSpecId.asString();
+		
+		if(_Strings.isNullOrEmpty(className)) {
+			return null;
+		}
+
+		try {
+			final Class<?> type = loadClassByName(className);
+			return loadSpecification(type, upTo);
+
+		} catch (final ClassNotFoundException e) {
+			final ObjectSpecification spec = cache.get(className); 
+			if (spec == null) {
+				throw new IsisException("No such class available: " + className);
+			}
+			return spec;
+		}
+	}
+
+	@Override
+	public ObjectSpecification loadSpecification(@Nullable final Class<?> type, final IntrospectionState upTo) {
+		
+		if(type==null) {
+			return null;
+		}
+		
+		requires(upTo, "upTo");
+		
+		val spec = internalLoadSpecification(type, null, upTo);
+		if(spec == null) {
+			return null;
+		}
+
+		// TODO: review, is this now needed?
+		//  We now create the ObjectSpecIdFacet immediately after creating the ObjectSpecification,
+		//  so the cache shouldn't need updating here also.
+		if(cache.isInitialized()) {
+			// umm.  It turns out that anonymous inner classes (eg org.estatio.dom.WithTitleGetter$ToString$1)
+			// don't have an ObjectSpecId; hence the guard.
+			if(spec.containsDoOpFacet(ObjectSpecIdFacet.class)) {
+				ObjectSpecId specId = spec.getSpecId();
+				if (cache.getByObjectType(specId) == null) {
+					cache.recache(spec);
+				}
+			}
+		}
+		return spec;
+	}
+
+	private ObjectSpecification internalLoadSpecification(
+			final Class<?> type,
+			final NatureOfService natureFallback,
+			final IntrospectionState upTo) {
+
+		final Class<?> substitutedType = classSubstitutor.getClass(type);
+		if (substitutedType == null) {
+			return null;
+		}
+
+		final String typeName = substitutedType.getName();
+		ObjectSpecification spec = cache.get(typeName);
+		if (spec != null) {
+			return spec;
+		}
+
+		//TODO[2033] don't block on long running code ... 'specSpi.introspectUpTo(upTo);'
+		synchronized (this) {
+			
+			spec = cache.get(typeName);
+			if (spec != null) {
+				return spec;
+			}
+
+			final ObjectSpecification specification = createSpecification(substitutedType, natureFallback);
+
+			// put into the cache prior to introspecting, to prevent
+			// infinite loops
+			cache.cache(typeName, specification);
+
+			final ObjectSpecificationAbstract specSpi = (ObjectSpecificationAbstract) specification;
+			specSpi.introspectUpTo(upTo);
+
+			return specification;
+		}
+	}
+
+	// -- LOOKUP
+
+	@Override
+	public List<ObjectSpecification> allSpecifications() {
+		return _Lists.newArrayList(allCachedSpecifications());
+	}
+
+	@Override
+	public ObjectSpecification lookupBySpecId(ObjectSpecId objectSpecId) {
+		if(!cache.isInitialized()) {
+			throw new IllegalStateException("Internal cache not yet initialized");
+		}
+		final ObjectSpecification objectSpecification = cache.getByObjectType(objectSpecId);
+		if(objectSpecification == null) {
+			// fallback
+			return loadSpecification(objectSpecId, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
+		}
+		return objectSpecification;
+	}
+
+
+	
+	// -- HELPER
+	
+	private IsisConfiguration getConfiguration() {
+		return _Config.getConfiguration();
+	}	
+
+	private Collection<ObjectSpecification> allCachedSpecifications() {
+		return cache.allSpecifications();
+	}
+
+	private Stream<BeanAdapter> streamBeans() {
+		final ServiceRegistry registry = MetaModelContext.current().getServiceRegistry();
+		return registry.streamRegisteredBeans();
+	}
+	
+	/**
+	 * Creates the appropriate type of {@link ObjectSpecification}.
+	 */
+	private ObjectSpecification createSpecification(
+			final Class<?> cls,
+			final NatureOfService fallback) {
+
+		// ... and create the specs
+		final ObjectSpecificationAbstract objectSpec;
+		if (FreeStandingList.class.isAssignableFrom(cls)) {
+
+			objectSpec = new ObjectSpecificationOnStandaloneList(facetProcessor, postProcessor);
+
+		} else {
+
+			final FacetedMethodsBuilderContext facetedMethodsBuilderContext =
+					new FacetedMethodsBuilderContext(
+							this, facetProcessor);
+
+			final NatureOfService natureOfServiceIfAny = natureOfServiceFrom(cls, fallback);
+
+			objectSpec = new ObjectSpecificationDefault(cls,
+					facetedMethodsBuilderContext,
+					facetProcessor, natureOfServiceIfAny, postProcessor);
+		}
+
+		return objectSpec;
+	}
+
+	private NatureOfService natureOfServiceFrom(
+			final Class<?> type,
+			final NatureOfService fallback) {
+		final DomainService domainServiceIfAny = type.getAnnotation(DomainService.class);
+		return domainServiceIfAny != null ? domainServiceIfAny.nature() : fallback;
+	}
+
+	private Class<?> loadClassByName(final String className) throws ClassNotFoundException {
+		final Class<?> builtIn = ClassUtil.getBuiltIn(className);
+		if (builtIn != null) {
+			return builtIn;
+		}
+		return ClassUtil.forName(className);
+	}
+	
+	private final static _Probe probe = _Probe.unlimited().label("SpecificationLoader");
+
+	private void logBefore(
+			final List<ObjectSpecification> specificationsFromRegistry,
+			final Collection<ObjectSpecification> cachedSpecifications) {
+		if(!log.isDebugEnabled()) {
+			return;
+		}
+		log.debug(String.format(
+				"specificationsFromRegistry.size = %d ; cachedSpecifications.size = %d",
+				specificationsFromRegistry.size(), cachedSpecifications.size()));
+
+		List<ObjectSpecification> registryNotCached = specificationsFromRegistry.stream()
+				.filter(spec -> !cachedSpecifications.contains(spec))
+				.collect(Collectors.toList());
+		List<ObjectSpecification> cachedNotRegistry = cachedSpecifications.stream()
+				.filter(spec -> !specificationsFromRegistry.contains(spec))
+				.collect(Collectors.toList());
+
+		log.debug(String.format(
+				"registryNotCached.size = %d ; cachedNotRegistry.size = %d",
+				registryNotCached.size(), cachedNotRegistry.size()));
+	}
+
+	private void logAfter(final Collection<ObjectSpecification> cachedSpecifications) {
+		if(!log.isDebugEnabled()) {
+			return;
+		}
+
+		final Collection<ObjectSpecification> cachedSpecificationsAfter = cache.allSpecifications();
+		List<ObjectSpecification> cachedAfterNotBefore = cachedSpecificationsAfter.stream()
+				.filter(spec -> !cachedSpecifications.contains(spec))
+				.collect(Collectors.toList());
+		log.debug(String.format("cachedSpecificationsAfter.size = %d ; cachedAfterNotBefore.size = %d",
+				cachedSpecificationsAfter.size(), cachedAfterNotBefore.size()));
+	}
+
+	private void introspect(final Collection<ObjectSpecification> specs, final IntrospectionState upTo) {
+		final List<Callable<Object>> callables = _Lists.newArrayList();
+		for (final ObjectSpecification specification : specs) {
+			Callable<Object> callable = new Callable<Object>() {
+				@Override
+				public Object call() {
+
+					final ObjectSpecificationAbstract specSpi = (ObjectSpecificationAbstract) specification;
+					specSpi.introspectUpTo(upTo);
+
+					return null;
+				}
+				public String toString() {
+					return String.format(
+							"%s: #introspectUpTo( %s )",
+							specification.getFullIdentifier(), upTo);
+				}
+			};
+			callables.add(callable);
+		}
+
+		invokeAndWait(callables);
+	}
+
+	private void invokeAndWait(final List<Callable<Object>> callables) {
+		final ThreadPoolSupport threadPoolSupport = ThreadPoolSupport.getInstance();
+		final boolean parallelize = CONFIG_PROPERTY_PARALLELIZE.from(getConfiguration());
+
+		final ThreadPoolExecutionMode executionModeFromConfig = parallelize
+				? ThreadPoolExecutionMode.PARALLEL
+						: ThreadPoolExecutionMode.SEQUENTIAL;
+
+		final List<Future<Object>> futures = 
+				threadPoolSupport.invokeAll(executionModeFromConfig, callables);
+		threadPoolSupport.joinGatherFailures(futures);
+	}
+
+	private List<ObjectSpecification> loadSpecificationsFor(
+			final Stream<Class<?>> domainTypes,
+			final NatureOfService natureOfServiceFallback,
+			final List<ObjectSpecification> appendTo,
+			final IntrospectionState upTo) {
+
+		return domainTypes
+				.map(domainType->internalLoadSpecification(domainType, natureOfServiceFallback, upTo))
+				.filter(_NullSafe::isPresent)
+				.peek(appendTo::add)
+				.collect(Collectors.toList());
+	}
+
+	private List<ObjectSpecification> loadSpecificationsForBeans (
+			final Stream<BeanAdapter> beans,
+			final NatureOfService natureOfServiceFallback,
+			final List<ObjectSpecification> appendTo,
+			final IntrospectionState upTo) {
+
+		return beans
+				.filter(bean->bean.isDomainService())
+				.map(bean->bean.getBean().getBeanClass())    
+				.map(domainType->internalLoadSpecification(domainType, natureOfServiceFallback, upTo))
+				.filter(_NullSafe::isPresent)
+				.peek(appendTo::add)
+				.collect(Collectors.toList());
+	}
+	
+	private void invalidateCache(final Class<?> cls) {
+
+		if(!cache.isInitialized()) {
+			// could be called by JRebel plugin, before we are up-and-running
+			// just ignore.
+			return;
+		}
+		final Class<?> substitutedType = classSubstitutor.getClass(cls);
+
+		if(substitutedType.isAnonymousClass()) {
+			// JRebel plugin might call us... just ignore 'em.
+			return;
+		}
+
+		ObjectSpecification spec = loadSpecification(substitutedType, IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
+		while(spec != null) {
+			final Class<?> type = spec.getCorrespondingClass();
+			cache.remove(type.getName());
+			if(spec.containsDoOpFacet(ObjectSpecIdFacet.class)) {
+				// umm.  Some specs do not have an ObjectSpecIdFacet...
+				recache(spec);
+			}
+			spec = spec.superclass();
+		}
+	}
+
+
+	private void recache(final ObjectSpecification newSpec) {
+		cache.recache(newSpec);
+	}
+	
+	// -- DEPRECATED
+
+//	/**
+//	 * Loads the specifications of the specified types except the one specified
+//	 * (to prevent an infinite loop).
+//	 */
+//	public boolean loadSpecifications(
+//			final List<Class<?>> typesToLoad,
+//			final Class<?> typeToIgnore,
+//			final IntrospectionState upTo) {
+//
+//		boolean anyLoadedAsNull = false;
+//		for (final Class<?> typeToLoad : typesToLoad) {
+//			if (typeToLoad != typeToIgnore) {
+//				final ObjectSpecification objectSpecification =
+//						internalLoadSpecification(typeToLoad, null, upTo);
+//				final boolean loadedAsNull = (objectSpecification == null);
+//				anyLoadedAsNull = loadedAsNull || anyLoadedAsNull;
+//			}
+//		}
+//		return anyLoadedAsNull;
+//	}
+	
+//	public ObjectSpecification peekSpecification(final Class<?> type) {
+//
+//		final Class<?> substitutedType = classSubstitutor.getClass(type);
+//		if (substitutedType == null) {
+//			return null;
+//		}
+//
+//		final String typeName = substitutedType.getName();
+//		ObjectSpecification spec = cache.get(typeName);
+//		if (spec != null) {
+//			return spec;
+//		}
+//
+//		return null;
+//	}
+	
+//	/**
+//	 * Whether this class has been loaded.
+//	 */
+//	private boolean loaded(final Class<?> cls) {
+//		return loaded(cls.getName());
+//	}
+	
+//	/**
+//	 * @see #loaded(Class).
+//	 */
+//	private boolean loaded(final String fullyQualifiedClassName) {
+//		return cache.get(fullyQualifiedClassName) != null;
+//	}
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
index 098300d..994d778 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
@@ -20,18 +20,15 @@
 package org.apache.isis.core.metamodel.specloader.specimpl;
 
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.apache.isis.applib.annotation.Action;
 import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.config.internal._Config;
 import org.apache.isis.core.commons.exceptions.IsisException;
 import org.apache.isis.core.commons.lang.ListExtensions;
@@ -53,6 +50,10 @@ import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
 import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor;
 import org.apache.isis.core.metamodel.specloader.traverser.SpecificationTraverser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import lombok.val;
 
 public class FacetedMethodsBuilder {
 
@@ -234,12 +235,15 @@ public class FacetedMethodsBuilder {
         final Set<Method> associationCandidateMethods = getFacetProcessor().findAssociationCandidateAccessors(methods, new HashSet<Method>());
 
         // Ensure all return types are known
-        final List<Class<?>> typesToLoad = _Lists.newArrayList();
+        final Set<Class<?>> typesToLoad = _Sets.newHashSet();
         for (final Method method : associationCandidateMethods) {
             specificationTraverser.traverseTypes(method, typesToLoad);
         }
-        getSpecificationLoader().loadSpecifications(typesToLoad, introspectedClass,
-                IntrospectionState.TYPE_INTROSPECTED);
+        typesToLoad.remove(introspectedClass);
+        
+        val specLoader = getSpecificationLoader();
+        val upTo = IntrospectionState.TYPE_INTROSPECTED;
+        typesToLoad.forEach(typeToLoad->specLoader.loadSpecification(typeToLoad, upTo));
 
         // now create FacetedMethods for collections and for properties
         final List<FacetedMethod> associationFacetedMethods = _Lists.newArrayList();
@@ -438,11 +442,16 @@ public class FacetedMethodsBuilder {
             return false;
         }
 
-        final List<Class<?>> typesToLoad = new ArrayList<Class<?>>();
+        final Set<Class<?>> typesToLoad = _Sets.newHashSet();
         specificationTraverser.traverseTypes(actionMethod, typesToLoad);
+        
+        val specLoader = getSpecificationLoader();
+        val upTo = IntrospectionState.TYPE_INTROSPECTED;
+        
+        val anyLoadedAsNull = typesToLoad.stream()
+        .map(typeToLoad->specLoader.loadSpecification(typeToLoad, upTo))
+        .anyMatch(spec->spec==null);
 
-        final boolean anyLoadedAsNull = getSpecificationLoader()
-                .loadSpecifications(typesToLoad, null, IntrospectionState.TYPE_INTROSPECTED);
         if (anyLoadedAsNull) {
             return false;
         }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/IntrospectionState.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/IntrospectionState.java
index e200ef5..1c28510 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/IntrospectionState.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/IntrospectionState.java
@@ -26,6 +26,7 @@ public enum IntrospectionState implements Comparable<IntrospectionState> {
      * Interim stage, to avoid infinite loops while on way to being {@link #TYPE_INTROSPECTED}
      */
     TYPE_BEING_INTROSPECTED,
+    
     /**
      * Type has been introspected (but not its members).
      */
@@ -35,6 +36,7 @@ public enum IntrospectionState implements Comparable<IntrospectionState> {
      * Interim stage, to avoid infinite loops while on way to being {@link #TYPE_AND_MEMBERS_INTROSPECTED}
      */
     MEMBERS_BEING_INTROSPECTED,
+    
     /**
      * Fully introspected... class and also its members.
      */
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index f8ee912..9faf81f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -622,8 +622,6 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
     }
 
 
-    private static ThreadLocal<Boolean> invalidatingCache = ThreadLocal.withInitial(() -> Boolean.FALSE);
-
     @Override
     public ObjectMember getMember(final String memberId) {
         introspectUpTo(IntrospectionState.TYPE_AND_MEMBERS_INTROSPECTED);
@@ -639,6 +637,9 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
         return null;
     }
 
+  //TODO [2033] remove or replace        
+//    private static ThreadLocal<Boolean> invalidatingCache = ThreadLocal.withInitial(() -> Boolean.FALSE);
+
 
     /**
      * The association with the given {@link ObjectAssociation#getId() id}.
@@ -662,27 +663,28 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
         if(oa != null) {
             return oa;
         }
-        if(_Context.isPrototyping()) {
-            // automatically refresh if not in production
-            // (better support for jrebel)
-
-            LOG.warn("Could not find association with id '{}'; invalidating cache automatically", id);
-            if(!invalidatingCache.get()) {
-                // make sure don't go into an infinite loop, though.
-                try {
-                    invalidatingCache.set(true);
-                    getSpecificationLoader().invalidateCache(getCorrespondingClass());
-                } finally {
-                    invalidatingCache.set(false);
-                }
-            } else {
-                LOG.warn("... already invalidating cache earlier in stacktrace, so skipped this time");
-            }
-            oa = getAssociationWithId(id);
-            if(oa != null) {
-                return oa;
-            }
-        }
+//TODO [2033] remove or replace        
+//        if(_Context.isPrototyping()) {
+//            // automatically refresh if not in production
+//            // (better support for jrebel)
+//
+//            LOG.warn("Could not find association with id '{}'; invalidating cache automatically", id);
+//            if(!invalidatingCache.get()) {
+//                // make sure don't go into an infinite loop, though.
+//                try {
+//                    invalidatingCache.set(true);
+//                    getSpecificationLoader().invalidateCache(getCorrespondingClass());
+//                } finally {
+//                    invalidatingCache.set(false);
+//                }
+//            } else {
+//                LOG.warn("... already invalidating cache earlier in stacktrace, so skipped this time");
+//            }
+//            oa = getAssociationWithId(id);
+//            if(oa != null) {
+//                return oa;
+//            }
+//        }
         throw new ObjectSpecificationException(
                 String.format("No association called '%s' in '%s'", id, getSingularName()));
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/traverser/SpecificationTraverser.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/traverser/SpecificationTraverser.java
index 4e25b78..7ee8317 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/traverser/SpecificationTraverser.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/traverser/SpecificationTraverser.java
@@ -20,7 +20,7 @@
 package org.apache.isis.core.metamodel.specloader.traverser;
 
 import java.lang.reflect.Method;
-import java.util.List;
+import java.util.Collection;
 
 public class SpecificationTraverser {
 
@@ -31,7 +31,7 @@ public class SpecificationTraverser {
      * It's possible for there to be multiple return types: the generic type,
      * and the parameterized type.
      */
-    public void traverseTypes(final Method method, final List<Class<?>> discoveredTypes) {
+    public void traverseTypes(final Method method, final Collection<Class<?>> discoveredTypes) {
         final TypeExtractorMethodReturn returnTypes = new TypeExtractorMethodReturn(method);
         for (final Class<?> returnType : returnTypes) {
             discoveredTypes.add(returnType);
diff --git a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/JavaReflectorHelper.java b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/JavaReflectorHelper.java
index a5a3676..e897c9c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/JavaReflectorHelper.java
+++ b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/JavaReflectorHelper.java
@@ -24,6 +24,7 @@ import java.util.Collection;
 import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
 import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
+import org.apache.isis.core.metamodel.specloader.SpecificationLoaderDefault;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidator;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
 
@@ -44,7 +45,7 @@ public final class JavaReflectorHelper  {
 
         programmingModel.refineMetaModelValidator(metaModelValidator);
 
-        return new SpecificationLoader(programmingModel, metaModelValidator);
+        return new SpecificationLoaderDefault(programmingModel, metaModelValidator);
     }
     
     
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Command.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Command.java
index 28e28b0..92edc63 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Command.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Command.java
@@ -15,7 +15,6 @@ import org.apache.isis.applib.annotation.CommandReification;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessMethodContext;
-import org.apache.isis.core.metamodel.facets.actions.action.ActionAnnotationFacetFactoryTest.SomeTransactionalId;
 import org.apache.isis.core.metamodel.facets.actions.action.command.CommandFacetForActionAnnotation;
 import org.apache.isis.core.metamodel.facets.actions.action.command.CommandFacetForActionAnnotationAsConfigured;
 import org.apache.isis.core.metamodel.facets.actions.action.command.CommandFacetFromConfiguration;
@@ -74,7 +73,7 @@ public class ActionAnnotationFacetFactoryTest_Command extends ActionAnnotationFa
         // then
         final Facet facet = facetedMethod.getFacet(CommandFacet.class);
         assertNotNull(facet);
-        assert(facet instanceof  CommandFacetFromConfiguration);
+        assertTrue(facet instanceof  CommandFacetFromConfiguration);
         final CommandFacetFromConfiguration facetImpl = (CommandFacetFromConfiguration) facet;
         assertThat(facetImpl.persistence(), is(org.apache.isis.applib.annotation.CommandPersistence.PERSISTED));
         assertThat(facetImpl.executeIn(), is(org.apache.isis.applib.annotation.CommandExecuteIn.FOREGROUND));
@@ -130,9 +129,7 @@ public class ActionAnnotationFacetFactoryTest_Command extends ActionAnnotationFa
     public void given_asConfigured_and_configurationSetToIgnoreQueryOnly_andSafeSemantics_thenNone() {
 
         class Customer {
-            @Action(
-                    command = CommandReification.AS_CONFIGURED
-            )
+            @Action(command = CommandReification.AS_CONFIGURED)
             public void someAction() {
             }
         }
@@ -182,9 +179,7 @@ public class ActionAnnotationFacetFactoryTest_Command extends ActionAnnotationFa
     public void given_asConfigured_and_configurationSetToIgnoreQueryOnly_andNoSemantics_thenException() {
 
         class Customer {
-            @Action(
-                    command = CommandReification.AS_CONFIGURED
-            )
+            @Action(command = CommandReification.AS_CONFIGURED)
             public void someAction() {
             }
         }
@@ -199,9 +194,7 @@ public class ActionAnnotationFacetFactoryTest_Command extends ActionAnnotationFa
     public void given_asConfigured_and_configurationSetToNone_thenNone() {
 
         class Customer {
-            @Action(
-                    command = CommandReification.AS_CONFIGURED
-            )
+            @Action(command = CommandReification.AS_CONFIGURED)
             public void someAction() {
             }
         }
@@ -248,9 +241,7 @@ public class ActionAnnotationFacetFactoryTest_Command extends ActionAnnotationFa
 
         // given
         class Customer {
-            @Action(
-                    command = CommandReification.ENABLED
-            )
+            @Action(command = CommandReification.ENABLED)
             public void someAction() {
             }
         }
@@ -273,9 +264,7 @@ public class ActionAnnotationFacetFactoryTest_Command extends ActionAnnotationFa
 
         // given
         class Customer {
-            @Action(
-                    command = CommandReification.DISABLED
-            )
+            @Action(command = CommandReification.DISABLED)
             public void someAction() {
             }
         }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Invocation.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Invocation.java
index 43e85ba..31bec29 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Invocation.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Invocation.java
@@ -25,7 +25,8 @@ public class ActionAnnotationFacetFactoryTest_Invocation extends ActionAnnotatio
 
         class Customer {
 
-			class SomeActionInvokedDomainEvent extends ActionDomainEvent<Customer> { }
+			class SomeActionInvokedDomainEvent extends ActionDomainEvent<Customer> {
+				private static final long serialVersionUID = 1L; }
 
             @Action(domainEvent = SomeActionInvokedDomainEvent.class)
             public void someAction() {
@@ -66,7 +67,8 @@ public class ActionAnnotationFacetFactoryTest_Invocation extends ActionAnnotatio
 
         class Customer {
 
-            class SomeActionInvokedDomainEvent extends ActionDomainEvent<Customer> { }
+            class SomeActionInvokedDomainEvent extends ActionDomainEvent<Customer> {
+				private static final long serialVersionUID = 1L; }
 
             @Action(domainEvent = SomeActionInvokedDomainEvent.class)
             public void someAction() {
@@ -109,7 +111,7 @@ public class ActionAnnotationFacetFactoryTest_Invocation extends ActionAnnotatio
         class Customer {
 
             class SomeActionInvokedDomainEvent extends ActionDomainEvent<Customer> {
-            }
+            	private static final long serialVersionUID = 1L; }
 
             @Action(domainEvent= SomeActionInvokedDomainEvent.class)
             public void someAction() {
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Publishing.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Publishing.java
index c7eee75..f7337fb 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Publishing.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactoryTest_Publishing.java
@@ -8,9 +8,9 @@ import java.lang.reflect.Method;
 
 import org.apache.isis.applib.annotation.Action;
 import org.apache.isis.applib.annotation.SemanticsOf;
+import org.apache.isis.commons.internal.base._Blackhole;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessMethodContext;
-import org.apache.isis.core.metamodel.facets.actions.action.ActionAnnotationFacetFactoryTest.SomeTransactionalId;
 import org.apache.isis.core.metamodel.facets.actions.action.publishing.PublishedActionFacetForActionAnnotation;
 import org.apache.isis.core.metamodel.facets.actions.action.publishing.PublishedActionFacetFromConfiguration;
 import org.apache.isis.core.metamodel.facets.actions.publish.PublishedActionFacet;
@@ -67,6 +67,7 @@ public class ActionAnnotationFacetFactoryTest_Publishing extends ActionAnnotatio
         final Facet facet = facetedMethod.getFacet(PublishedActionFacet.class);
         assertNotNull(facet);
         final PublishedActionFacetFromConfiguration facetImpl = (PublishedActionFacetFromConfiguration) facet;
+        _Blackhole.consume(facetImpl);
     }
 
     @Test(expected=IllegalStateException.class)
@@ -165,7 +166,8 @@ public class ActionAnnotationFacetFactoryTest_Publishing extends ActionAnnotatio
         final Facet facet = facetedMethod.getFacet(PublishedActionFacet.class);
         assertNotNull(facet);
         final PublishedActionFacetForActionAnnotation facetImpl = (PublishedActionFacetForActionAnnotation) facet;
-
+        _Blackhole.consume(facetImpl);
+        
         expectNoMethodsRemoved();
     }
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderTestAbstract.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderTestAbstract.java
index 3963d2e..b0343cb 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderTestAbstract.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/SpecificationLoaderTestAbstract.java
@@ -90,7 +90,7 @@ abstract class SpecificationLoaderTestAbstract {
         
         @Produces
         SpecificationLoader getSpecificationLoader() {
-            return new SpecificationLoader(
+            return new SpecificationLoaderDefault(
                     new ProgrammingModelFacetsJava5(DeprecatedPolicy.HONOUR),
                     new MetaModelValidatorDefault());
         }
diff --git a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
index 8f7e57e..7b37147 100644
--- a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
+++ b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
@@ -30,8 +30,8 @@ import javax.inject.Inject;
 import javax.inject.Singleton;
 
 import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.services.background.BackgroundCommandService;
-import org.apache.isis.applib.services.background.BackgroundService;
+import org.apache.isis.applib.services.background.CommandSchedulerService;
+import org.apache.isis.applib.services.background.BackgroundExecutionService;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.applib.services.factory.FactoryService;
 import org.apache.isis.commons.internal._Constants;
@@ -41,18 +41,14 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.classsubstitutor.ProxyEnhanced;
 import org.apache.isis.core.plugins.codegen.ProxyFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * For command-reification depends on an implementation of
- * {@link org.apache.isis.applib.services.background.BackgroundCommandService} to
+ * {@link org.apache.isis.applib.services.background.CommandSchedulerService} to
  * be configured.
  */
 @Singleton
-public class BackgroundServiceDefault implements BackgroundService {
-
-    static final Logger LOG = LoggerFactory.getLogger(BackgroundServiceDefault.class);
+public class BackgroundServiceDefault implements BackgroundExecutionService {
 
     // only used if there is no BackgroundCommandService
     static class BuiltinExecutor {
@@ -172,7 +168,7 @@ public class BackgroundServiceDefault implements BackgroundService {
 
     // //////////////////////////////////////
 
-    @Inject @Any private Instance<BackgroundCommandService> backgroundCommandServices;
+    @Inject @Any private Instance<CommandSchedulerService> backgroundCommandServices;
     @Inject private CommandDtoServiceInternal commandDtoServiceInternal;
     @Inject private CommandContext commandContext;
     @Inject private FactoryService factoryService;
diff --git a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/CommandInvocationHandler.java b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/CommandInvocationHandler.java
index 36295bb..1c1287a 100644
--- a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/CommandInvocationHandler.java
+++ b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/CommandInvocationHandler.java
@@ -25,7 +25,7 @@ import java.util.List;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
-import org.apache.isis.applib.services.background.BackgroundCommandService;
+import org.apache.isis.applib.services.background.CommandSchedulerService;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.core.metamodel.MetaModelContext;
@@ -44,7 +44,7 @@ import org.apache.isis.schema.cmd.v1.CommandDto;
 
 class CommandInvocationHandler<T> implements InvocationHandler {
 
-    private final BackgroundCommandService backgroundCommandService;
+    private final CommandSchedulerService backgroundCommandService;
     private final T target;
     private final Object mixedInIfAny;
     private final SpecificationLoader specificationLoader;
@@ -53,7 +53,7 @@ class CommandInvocationHandler<T> implements InvocationHandler {
     private final Supplier<ObjectAdapterProvider> adapterProviderSupplier;
 
     CommandInvocationHandler(
-            BackgroundCommandService backgroundCommandService,
+            CommandSchedulerService backgroundCommandService,
             T target,
             Object mixedInIfAny,
             SpecificationLoader specificationLoader,
diff --git a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java
index 175f3bd..8ef5388 100644
--- a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java
+++ b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/background/ForkingInvocationHandler.java
@@ -31,10 +31,13 @@ import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
 import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
 import org.apache.isis.core.security.authentication.AuthenticationSession;
 
+import lombok.extern.slf4j.Slf4j;
+
 /**
  * Package private invocation handler that executes actions in the background using a ExecutorService
  * @since 2.0.0
  */
+@Slf4j
 class ForkingInvocationHandler<T> implements InvocationHandler {
 
     private final T target;
@@ -90,8 +93,7 @@ class ForkingInvocationHandler<T> implements InvocationHandler {
                         authSession	);
 
             } catch (Exception e) {
-                // log in caller's context
-                BackgroundServiceDefault.LOG.error(
+                log.error(
                         String.format("Background execution of action '%s' on object '%s' failed.",
                                 proxyMethod.getName(),
                                 domainObject.getClass().getName()),
diff --git a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/factory/FactoryServiceInternalDefault.java b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/factory/FactoryServiceInternalDefault.java
index 3d4ca78..31f9ec3 100644
--- a/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/factory/FactoryServiceInternalDefault.java
+++ b/core/runtime-services/src/main/java/org/apache/isis/core/runtime/services/factory/FactoryServiceInternalDefault.java
@@ -53,12 +53,6 @@ public class FactoryServiceInternalDefault implements FactoryService {
     	return objectAdapterProvider.newTransientInstance(spec);
     }
 
-
-    @Override
-    public <T> T m(final Class<T> mixinClass, final Object mixedIn) {
-        return mixin(mixinClass, mixedIn);
-    }
-
     @Override
     public <T> T mixin(final Class<T> mixinClass, final Object mixedIn) {
         final ObjectSpecification objectSpec = specificationLoader.loadSpecification(mixinClass);
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/memento/Memento.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/memento/Memento.java
index 06da33d..ada069a 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/memento/Memento.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/memento/Memento.java
@@ -34,6 +34,7 @@ import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
 import org.apache.isis.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
 import org.apache.isis.core.metamodel.facets.properties.update.modify.PropertySetterFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecId;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
@@ -193,7 +194,7 @@ public class Memento implements Serializable {
             return null;
         }
         final ObjectSpecification spec =
-                getSpecificationLoader().loadSpecification(data.getClassName());
+                getSpecificationLoader().loadSpecification(ObjectSpecId.of(data.getClassName()));
         
         final Oid oid = getOid();
 
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/memento/StandaloneData.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/memento/StandaloneData.java
index 150ac7d..1f89b19 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/memento/StandaloneData.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/memento/StandaloneData.java
@@ -28,6 +28,7 @@ import org.apache.isis.core.commons.encoding.DataInputExtended;
 import org.apache.isis.core.commons.encoding.DataOutputExtended;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecId;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 
@@ -120,7 +121,8 @@ public class StandaloneData extends Data {
         if (objectAsSerializable != null) {
             return IsisContext.pojoToAdapter().apply(objectAsSerializable);
         } else {
-            final ObjectSpecification spec = getSpecificationLoader().loadSpecification(getClassName());
+            final ObjectSpecification spec = 
+            		getSpecificationLoader().loadSpecification(ObjectSpecId.of(getClassName()));
             final EncodableFacet encodeableFacet = spec.getFacet(EncodableFacet.class);
             return encodeableFacet.fromEncodedString(objectAsEncodedString);
         }
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/CollectionMemento.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/CollectionMemento.java
index a1bc24b..54aae5d 100644
--- a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/CollectionMemento.java
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/CollectionMemento.java
@@ -30,8 +30,8 @@ import org.apache.isis.core.runtime.system.context.IsisContext;
 import lombok.val;
 
 /**
- * {@link Serializable} representation of a {@link OneToManyAssociation} (a
- * parented collection of entities).
+ * {@link Serializable} representation of a {@link OneToManyAssociation} 
+ * (a parented collection of entities).
  */
 public class CollectionMemento implements Serializable {
 
@@ -41,7 +41,8 @@ public class CollectionMemento implements Serializable {
             final OneToManyAssociation association) {
     	
         val specificationLoader = IsisContext.getSpecificationLoader();
-        return specificationLoader.loadSpecification(association.getIdentifier().toClassIdentityString());
+        return specificationLoader.loadSpecification(
+        		ObjectSpecId.of(association.getIdentifier().toClassIdentityString()));
     }
 
     private final ObjectSpecId owningType;
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/PropertyMemento.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/PropertyMemento.java
index 47cb086..f9dbc80 100644
--- a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/PropertyMemento.java
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/PropertyMemento.java
@@ -37,7 +37,8 @@ public class PropertyMemento implements Serializable {
     private static ObjectSpecification owningSpecFor(
             final OneToOneAssociation property) {
         val specificationLoader = IsisContext.getSpecificationLoader();
-        return specificationLoader.loadSpecification(property.getIdentifier().toClassIdentityString());
+        return specificationLoader.loadSpecification(
+        		ObjectSpecId.of(property.getIdentifier().toClassIdentityString()));
     }
 
     private final ObjectSpecId owningSpecId;
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/SpecUtils.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/SpecUtils.java
index e2165b4..b9742da 100644
--- a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/SpecUtils.java
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/mementos/SpecUtils.java
@@ -22,6 +22,7 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecId;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 
+@Deprecated //TODO [2033] adds no value
 public final class SpecUtils {
 
     private SpecUtils(){}
@@ -29,13 +30,8 @@ public final class SpecUtils {
     public static ObjectSpecification getSpecificationFor(
             final ObjectSpecId objectSpecId,
             final SpecificationLoader specificationLoader) {
-        ObjectSpecification objectSpec = specificationLoader.lookupBySpecId(objectSpecId);
-        if(objectSpec != null) {
-            return objectSpec;
-        }
-
-        // attempt to load directly.
-        return specificationLoader.loadSpecification(objectSpecId.asString());
+    	
+        return specificationLoader.lookupBySpecId(objectSpecId);
     }
 
 
diff --git a/example/application/springapp/src/main/java/isis/incubator/IsisBoot.java b/example/application/springapp/src/main/java/isis/incubator/IsisBoot.java
index 7d2b796..3cee4e4 100644
--- a/example/application/springapp/src/main/java/isis/incubator/IsisBoot.java
+++ b/example/application/springapp/src/main/java/isis/incubator/IsisBoot.java
@@ -28,6 +28,7 @@ import springapp.boot.web.SpringAppManifest;
 @Configuration 
 @ComponentScan(
 		basePackageClasses= {
+				IsisIncubatorModule.class,
 				IsisApplibModule.class,
 				MetamodelModule.class,
 				RuntimeModule.class,
diff --git a/example/application/springapp/src/main/java/isis/incubator/IsisIncubatorModule.java b/example/application/springapp/src/main/java/isis/incubator/IsisIncubatorModule.java
new file mode 100644
index 0000000..aaa7108
--- /dev/null
+++ b/example/application/springapp/src/main/java/isis/incubator/IsisIncubatorModule.java
@@ -0,0 +1,5 @@
+package isis.incubator;
+
+final class IsisIncubatorModule {
+
+}
diff --git a/example/application/springapp/src/main/java/isis/incubator/command/CommandSchedulerServiceInMemory.java b/example/application/springapp/src/main/java/isis/incubator/command/CommandSchedulerServiceInMemory.java
new file mode 100644
index 0000000..35132f2
--- /dev/null
+++ b/example/application/springapp/src/main/java/isis/incubator/command/CommandSchedulerServiceInMemory.java
@@ -0,0 +1,23 @@
+package isis.incubator.command;
+
+import org.apache.isis.applib.services.background.CommandSchedulerService;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.schema.cmd.v1.CommandDto;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CommandSchedulerServiceInMemory implements CommandSchedulerService {
+
+	@Override
+	public void schedule(
+			CommandDto dto, 
+			Command parentCommand, 
+			String targetClassName, 
+			String targetActionName,
+			String targetArgs) {
+		
+		// TODO Auto-generated method stub
+		
+	}
+
+}
diff --git a/example/application/springapp/src/main/java/isis/incubator/command/IncubatingBackgroundExecutionService.java b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingBackgroundExecutionService.java
new file mode 100644
index 0000000..3ae2c05
--- /dev/null
+++ b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingBackgroundExecutionService.java
@@ -0,0 +1,92 @@
+package isis.incubator.command;
+
+import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
+
+import java.lang.reflect.InvocationHandler;
+
+import javax.inject.Inject;
+
+import org.apache.isis.applib.services.background.BackgroundExecutionService;
+import org.apache.isis.applib.services.background.CommandSchedulerService;
+import org.apache.isis.applib.services.command.CommandContext;
+import org.apache.isis.applib.services.factory.FactoryService;
+import org.apache.isis.commons.internal._Constants;
+import org.apache.isis.core.commons.lang.ArrayExtensions;
+import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal;
+import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
+import org.apache.isis.core.metamodel.specloader.classsubstitutor.ProxyEnhanced;
+import org.apache.isis.core.plugins.codegen.ProxyFactory;
+import org.springframework.stereotype.Service;
+
+@Service
+public class IncubatingBackgroundExecutionService implements BackgroundExecutionService {
+
+	@Override
+    public <T> T execute(final T domainObject) {
+        final Class<T> cls = uncheckedCast(domainObject.getClass());
+        final InvocationHandler methodHandler = newMethodHandler(domainObject, null);
+        return newProxy(cls, null, methodHandler);
+    }
+
+    @Override
+    public <T> T executeMixin(Class<T> mixinClass, Object mixedIn) {
+        final T mixin = factoryService.mixin(mixinClass, mixedIn);
+        final InvocationHandler methodHandler = newMethodHandler(mixin, mixedIn);
+        return newProxy(mixinClass, mixedIn, methodHandler);
+    }
+
+    private <T> T newProxy(
+            final Class<T> cls,
+            final Object mixedInIfAny,
+            final InvocationHandler methodHandler) {
+
+        final Class<?>[] interfaces = ArrayExtensions.combine(
+                cls.getInterfaces(),
+                new Class<?>[] { ProxyEnhanced.class });
+
+        final boolean initialize = mixedInIfAny!=null;
+
+
+        final Class<?>[] constructorArgTypes = initialize ? new Class<?>[] {mixedInIfAny.getClass()} : _Constants.emptyClasses;
+        final Object[] constructorArgs = initialize ? new Object[] {mixedInIfAny} : _Constants.emptyObjects;
+
+        final ProxyFactory<T> proxyFactory = ProxyFactory.builder(cls)
+                .interfaces(interfaces)
+                .constructorArgTypes(constructorArgTypes)
+                .build();
+
+        return initialize
+                ? proxyFactory.createInstance(methodHandler, constructorArgs)
+                        : proxyFactory.createInstance(methodHandler, false)
+                        ;
+    }
+
+    /**
+     *
+     * @param target - the object that is proxied, either a domain object or a mixin around a domain object
+     * @param mixedInIfAny - if target is a mixin, then this is the domain object that is mixed-in to.
+     */
+    private <T> InvocationHandler newMethodHandler(final T target, final Object mixedInIfAny) {
+
+    	return IncubatingCommandInvocationHandler.builder()
+    			.commandSchedulerService(commandSchedulerService)
+    			.target(target)
+    			.mixedInIfAny(mixedInIfAny)
+    			.specificationLoader(specificationLoader)
+    			.commandDtoServiceInternal(commandDtoServiceInternal)
+    			.toplevelCommandSupplier(new CommandContext())
+    			.build();
+        
+    }
+
+
+    // //////////////////////////////////////
+
+    //@Inject @Any private Instance<BackgroundCommandService> backgroundCommandServices;
+    @Inject private CommandSchedulerService commandSchedulerService;
+    @Inject private CommandDtoServiceInternal commandDtoServiceInternal;
+    //@Inject private CommandContext commandContext;
+    @Inject private FactoryService factoryService;
+    @Inject private SpecificationLoader specificationLoader;
+
+}
diff --git a/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandDtoServiceInternal.java b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandDtoServiceInternal.java
new file mode 100644
index 0000000..fb5a0cf
--- /dev/null
+++ b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandDtoServiceInternal.java
@@ -0,0 +1,56 @@
+package isis.incubator.command;
+
+import java.util.List;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.schema.cmd.v1.ActionDto;
+import org.apache.isis.schema.cmd.v1.CommandDto;
+import org.apache.isis.schema.cmd.v1.PropertyDto;
+import org.springframework.stereotype.Service;
+
+@Service
+public class IncubatingCommandDtoServiceInternal implements CommandDtoServiceInternal {
+
+	@Override
+	public CommandDto asCommandDto(
+			List<ObjectAdapter> targetAdapters, 
+			ObjectAction objectAction,
+			ObjectAdapter[] argAdapters) {
+		
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public CommandDto asCommandDto(
+			List<ObjectAdapter> targetAdapters, 
+			OneToOneAssociation association,
+			ObjectAdapter valueAdapterOrNull) {
+		
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public void addActionArgs(
+			ObjectAction objectAction, 
+			ActionDto actionDto, 
+			ObjectAdapter[] argAdapters) {
+		
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	public void addPropertyValue(
+			OneToOneAssociation property, 
+			PropertyDto propertyDto, 
+			ObjectAdapter valueAdapter) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}
diff --git a/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandInvocationHandler.java b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandInvocationHandler.java
new file mode 100644
index 0000000..bd9c084
--- /dev/null
+++ b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingCommandInvocationHandler.java
@@ -0,0 +1,33 @@
+package isis.incubator.command;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+
+import org.apache.isis.applib.services.background.CommandSchedulerService;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapterProvider;
+import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal;
+import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
+
+import lombok.Builder;
+import lombok.NonNull;
+
+@Builder
+class IncubatingCommandInvocationHandler<T> implements InvocationHandler {
+	
+	@NonNull private final CommandSchedulerService commandSchedulerService;
+	@NonNull private final T target;
+    private final Object mixedInIfAny;
+    @NonNull private final SpecificationLoader specificationLoader;
+    @NonNull private final CommandDtoServiceInternal commandDtoServiceInternal;
+    @NonNull private final Supplier<Command> toplevelCommandSupplier;
+    @NonNull private final Supplier<ObjectAdapterProvider> adapterProviderSupplier;
+
+	@Override
+	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+		// TODO Auto-generated method stub
+		return null;
+	}
+	
+}
diff --git a/example/application/springapp/src/main/java/isis/incubator/command/IncubatingFactoryService.java b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingFactoryService.java
new file mode 100644
index 0000000..1ddbb66
--- /dev/null
+++ b/example/application/springapp/src/main/java/isis/incubator/command/IncubatingFactoryService.java
@@ -0,0 +1,21 @@
+package isis.incubator.command;
+
+import org.apache.isis.applib.services.factory.FactoryService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class IncubatingFactoryService implements FactoryService {
+
+	@Override
+	public <T> T instantiate(Class<T> domainClass) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public <T> T mixin(Class<T> mixinClass, Object mixedIn) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+}
diff --git a/example/application/springapp/src/main/java/springapp/dom/customer/Customer.java b/example/application/springapp/src/main/java/springapp/dom/customer/Customer.java
index 1f0c9f1..11c41d8 100644
--- a/example/application/springapp/src/main/java/springapp/dom/customer/Customer.java
+++ b/example/application/springapp/src/main/java/springapp/dom/customer/Customer.java
@@ -1,32 +1,39 @@
 package springapp.dom.customer;
 
+import java.util.List;
+
 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
+import javax.persistence.OneToMany;
 
 import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.annotation.Nature;
 
+import lombok.AccessLevel;
 import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
 import lombok.ToString;
+import springapp.dom.email.Email;
 
 @DomainObject(nature=Nature.EXTERNAL_ENTITY)
-@Entity @ToString
+@Entity 
+@NoArgsConstructor(access = AccessLevel.PROTECTED) @RequiredArgsConstructor @ToString
 public class Customer {
 
     @Id
     @GeneratedValue(strategy=GenerationType.AUTO)
     @Getter private Long id;
     
-    @Getter private String firstName;
-    @Getter private String lastName;
-
-    protected Customer() {}
-
-    public Customer(String firstName, String lastName) {
-        this.firstName = firstName;
-        this.lastName = lastName;
-    }
+    @Getter @NonNull private String firstName;
+    @Getter @NonNull private String lastName;
+    
+    @OneToMany(mappedBy = "customer")
+    @Getter @Setter 
+    private List<Email> emails;
 
 }
diff --git a/example/application/springapp/src/main/java/springapp/dom/customer/CustomerRepository.java b/example/application/springapp/src/main/java/springapp/dom/customer/CustomerRepository.java
index 362e069..e40c9c2 100644
--- a/example/application/springapp/src/main/java/springapp/dom/customer/CustomerRepository.java
+++ b/example/application/springapp/src/main/java/springapp/dom/customer/CustomerRepository.java
@@ -5,5 +5,7 @@ import java.util.List;
 import org.springframework.data.repository.CrudRepository;
 
 public interface CustomerRepository extends CrudRepository<Customer, Long> {
+	
     List<Customer> findByLastName(String lastName);
+    
 }
diff --git a/example/application/springapp/src/main/java/springapp/dom/email/Email.java b/example/application/springapp/src/main/java/springapp/dom/email/Email.java
new file mode 100644
index 0000000..cb7f4d9
--- /dev/null
+++ b/example/application/springapp/src/main/java/springapp/dom/email/Email.java
@@ -0,0 +1,49 @@
+package springapp.dom.email;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Nature;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import springapp.dom.customer.Customer;
+
+@DomainObject(nature=Nature.EXTERNAL_ENTITY)
+@Entity 
+@NoArgsConstructor(access = AccessLevel.PROTECTED) @RequiredArgsConstructor @ToString
+public class Email {
+
+    @Id
+    @GeneratedValue(strategy=GenerationType.AUTO)
+    @Getter 
+    private Long id;
+    
+    @ManyToOne
+    @Getter @Setter @NonNull 
+    private Customer customer;
+    
+    @Getter @Setter @NonNull
+    private String address;
+
+    @Getter @Setter 
+    private boolean verified;
+    
+    // -- BUSINESS LOGIC
+
+	public void startVerificationProcess() {
+		
+		System.out.println("startVerificationProcess for " + this);
+	}
+   
+    
+}
diff --git a/example/application/springapp/src/main/java/springapp/dom/email/EmailRepository.java b/example/application/springapp/src/main/java/springapp/dom/email/EmailRepository.java
new file mode 100644
index 0000000..4aee42e
--- /dev/null
+++ b/example/application/springapp/src/main/java/springapp/dom/email/EmailRepository.java
@@ -0,0 +1,11 @@
+package springapp.dom.email;
+
+import java.util.List;
+
+import org.springframework.data.repository.CrudRepository;
+
+public interface EmailRepository extends CrudRepository<Email, Long> {
+	
+	List<Email> findByVerified(boolean verified);
+	
+}
diff --git a/example/application/springapp/src/test/java/springapp/tests/command/CommandDemoBean.java b/example/application/springapp/src/test/java/springapp/tests/command/CommandDemoBean.java
new file mode 100644
index 0000000..a4521d2
--- /dev/null
+++ b/example/application/springapp/src/test/java/springapp/tests/command/CommandDemoBean.java
@@ -0,0 +1,29 @@
+package springapp.tests.command;
+
+import javax.inject.Inject;
+
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.services.background.BackgroundExecutionService;
+import org.springframework.stereotype.Service;
+
+import springapp.dom.customer.CustomerRepository;
+import springapp.dom.email.Email;
+import springapp.dom.email.EmailRepository;
+
+@Service
+public class CommandDemoBean {
+	
+	@Inject EmailRepository emailRepository;
+	@Inject CustomerRepository customerRepository;
+	@Inject BackgroundExecutionService backgroundService;
+	
+	@Action
+	public void verifyCustomerEmails() {
+		
+		for(Email email: emailRepository.findByVerified(false)) {
+			backgroundService.execute(email).startVerificationProcess();
+		}
+	    
+	}
+
+}
diff --git a/example/application/springapp/src/test/java/springapp/tests/command/CommandTest.java b/example/application/springapp/src/test/java/springapp/tests/command/CommandTest.java
new file mode 100644
index 0000000..99c3c1c
--- /dev/null
+++ b/example/application/springapp/src/test/java/springapp/tests/command/CommandTest.java
@@ -0,0 +1,30 @@
+package springapp.tests.command;
+
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import springapp.boot.test.SpringBootTestApplication;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(classes = {SpringBootTestApplication.class, CommandDemoBean.class})
+@AutoConfigureTestDatabase
+@EnableAsync
+@TestMethodOrder(OrderAnnotation.class)
+class CommandTest {
+
+	//@Inject AsyncExecutionService asyncExecutionService;
+
+	@Test
+	void shouldAllowTaskCancellation() {
+
+
+	}
+
+
+}
diff --git a/example/application/springapp/src/test/java/springapp/tests/command/MessageBox.java b/example/application/springapp/src/test/java/springapp/tests/command/MessageBox.java
new file mode 100644
index 0000000..480d658
--- /dev/null
+++ b/example/application/springapp/src/test/java/springapp/tests/command/MessageBox.java
@@ -0,0 +1,27 @@
+package springapp.tests.command;
+
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.isis.commons.internal.collections._Multimaps;
+import org.springframework.stereotype.Service;
+
+import lombok.val;
+
+@Service
+public class MessageBox {
+
+	private final _Multimaps.SetMultimap<String, String> store = _Multimaps.newSetMultimap();
+	
+	public void send(String key, String msg) {
+		synchronized (store) {
+			store.putElement(key, msg);	
+		}
+	}
+	
+	public Stream<String> streamMessages(String key) {
+		val messages = store.getOrDefault(key, Collections.emptySet());
+		return messages.stream();
+	}
+	
+}