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/10/09 14:17:44 UTC

[isis] 03/04: ISIS-2158: polish meta-model life cycle

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

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

commit 554c93f85b628755e9e63bbb6e57fb54cb71780d
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Oct 9 12:16:03 2019 +0200

    ISIS-2158: polish meta-model life cycle
---
 .../facets/object/mixin/MixinIntendedAs.java       |   2 +-
 .../ViewModelSemanticCheckingFacetFactory.java     |   3 +-
 .../DomainObjectAnnotationFacetFactory.java        |   6 +-
 .../metamodel/specloader/ServiceInitializer.java   | 181 ---------------
 .../metamodel/specloader/SpecificationLoader.java  |  64 ++----
 .../specloader/SpecificationLoaderDefault.java     | 110 +++------
 .../specloader/postprocessor/PostProcessor.java    |  12 +-
 .../specloader/validator/MetaModelValidator.java   |   7 +
 .../MetaModelValidatorForValidationFailures.java   |  24 +-
 .../specloader/ServiceInitializerTest.java         | 249 ---------------------
 .../SpecificationLoaderTestAbstract.java           |   7 +-
 .../service/JdoPersistenceLifecycleService.java    |  12 +-
 .../system/session/IsisSessionFactoryDefault.java  |  47 +---
 .../domainmodel/SpecloaderPerformanceTest.java     |   6 +-
 14 files changed, 114 insertions(+), 616 deletions(-)

diff --git a/core/detached-tests/src/test/java/org/apache/isis/metamodel/facets/object/mixin/MixinIntendedAs.java b/core/detached-tests/src/test/java/org/apache/isis/metamodel/facets/object/mixin/MixinIntendedAs.java
index ed2c39f..64a6843 100644
--- a/core/detached-tests/src/test/java/org/apache/isis/metamodel/facets/object/mixin/MixinIntendedAs.java
+++ b/core/detached-tests/src/test/java/org/apache/isis/metamodel/facets/object/mixin/MixinIntendedAs.java
@@ -58,7 +58,7 @@ abstract class MixinIntendedAs {
                 .build());
         
         ((ProgrammingModelAbstract)programmingModel).init(new ProgrammingModelInitFilterDefault());
-        MetaModelContext.current().getSpecificationLoader().init();
+        MetaModelContext.current().getSpecificationLoader().createMetaModel();
         
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/ViewModelSemanticCheckingFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/ViewModelSemanticCheckingFacetFactory.java
index 394afe2..ea9b7d9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/ViewModelSemanticCheckingFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/ViewModelSemanticCheckingFacetFactory.java
@@ -42,7 +42,8 @@ implements MetaModelRefiner {
         super(FeatureType.OBJECTS_ONLY);
     }
 
-    private final MetaModelValidatorForValidationFailures validator = new MetaModelValidatorForValidationFailures();
+    private final MetaModelValidatorForValidationFailures validator = 
+            new MetaModelValidatorForValidationFailures();
 
     @Override
     public void process(final ProcessClassContext processClassContext) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
index db1fbb5..3fc4b7b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
@@ -84,8 +84,10 @@ import lombok.val;
 public class DomainObjectAnnotationFacetFactory extends FacetFactoryAbstract
 implements MetaModelRefiner, PostConstructMethodCache, ObjectSpecIdFacetFactory {
 
-    private final MetaModelValidatorForValidationFailures autoCompleteMethodInvalid = new MetaModelValidatorForValidationFailures();
-    private final MetaModelValidatorForMixinTypes mixinTypeValidator = new MetaModelValidatorForMixinTypes("@DomainObject#nature=MIXIN");
+    private final MetaModelValidatorForValidationFailures autoCompleteMethodInvalid = 
+            new MetaModelValidatorForValidationFailures();
+    private final MetaModelValidatorForMixinTypes mixinTypeValidator = 
+            new MetaModelValidatorForMixinTypes("@DomainObject#nature=MIXIN");
 
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/ServiceInitializer.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/ServiceInitializer.java
deleted file mode 100644
index 02a78ee..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/ServiceInitializer.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.metamodel.specloader;
-
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-
-import org.apache.isis.commons.internal.collections._Maps;
-import org.apache.isis.config.IsisConfigurationLegacy;
-import org.apache.isis.metamodel.commons.MethodExtensions;
-
-import lombok.extern.log4j.Log4j2;
-
-@Log4j2 @Deprecated //TODO[2125] remove
-class ServiceInitializer {
-
-    private final List<Object> services;
-
-    private final Map<String, String> props;
-
-    private Map<Object, Method> postConstructMethodsByService = _Maps.newLinkedHashMap();
-    private Map<Object, Method> preDestroyMethodsByService = _Maps.newLinkedHashMap();
-
-    public ServiceInitializer(
-            final IsisConfigurationLegacy configuration,
-            final List<Object> services) {
-        this.props = configuration.copyToMap();
-        this.services = services;
-    }
-
-    // -- validate
-
-    public void validate() {
-
-        for (final Object service : services) {
-            log.debug("checking for @PostConstruct and @PostDestroy methods on {}", service.getClass().getName());
-            final Method[] methods = service.getClass().getMethods();
-
-            // @PostConstruct
-            for (final Method method : methods) {
-
-                final PostConstruct postConstructAnnotation = method.getAnnotation(PostConstruct.class);
-                if(postConstructAnnotation == null) {
-                    continue;
-                }
-                final Method existing = postConstructMethodsByService.get(service);
-                if(existing != null) {
-                    throw new RuntimeException("Found more than one @PostConstruct method; service is: " + service.getClass().getName() + ", found " + existing.getName() + " and " + method.getName());
-                }
-
-                final Class<?>[] parameterTypes = method.getParameterTypes();
-                switch(parameterTypes.length) {
-                case 0:
-                    break;
-                case 1:
-                    if(Map.class != parameterTypes[0]) {
-                        throw new RuntimeException("@PostConstruct method must be no-arg or 1-arg accepting java.util.Map; method is: " + service.getClass().getName() + "#" + method.getName());
-                    }
-                    break;
-                default:
-                    throw new RuntimeException("@PostConstruct method must be no-arg or 1-arg accepting java.util.Map; method is: " + service.getClass().getName() + "#" + method.getName());
-                }
-                postConstructMethodsByService.put(service, method);
-            }
-
-            // @PreDestroy
-            for (final Method method : methods) {
-                final PreDestroy preDestroyAnnotation = method.getAnnotation(PreDestroy.class);
-                if(preDestroyAnnotation == null) {
-                    continue;
-                }
-                final Method existing = preDestroyMethodsByService.get(service);
-                if(existing != null) {
-                    throw new RuntimeException("Found more than one @PreDestroy method; service is: " + service.getClass().getName() + ", found " + existing.getName() + " and " + method.getName());
-                }
-
-                final Class<?>[] parameterTypes = method.getParameterTypes();
-                switch(parameterTypes.length) {
-                case 0:
-                    break;
-                default:
-                    throw new RuntimeException("@PreDestroy method must be no-arg; method is: " + service.getClass().getName() + "#" + method.getName());
-                }
-                preDestroyMethodsByService.put(service, method);
-            }
-        }
-
-    }
-
-
-    // -- postConstruct
-
-    public void postConstruct() {
-        if(log.isInfoEnabled()) {
-            log.info("calling @PostConstruct on all domain services");
-        }
-
-        Exception firstExceptionIfAny = null;
-        for (final Map.Entry<Object, Method> entry : postConstructMethodsByService.entrySet()) {
-            final Object service = entry.getKey();
-            final Method method = entry.getValue();
-
-            if(log.isDebugEnabled()) {
-                log.debug(
-                        "... calling @PostConstruct method: " + service.getClass().getName() + ": " + method.getName());
-            }
-
-            final int numParams = method.getParameterTypes().length;
-
-            // unlike shutdown, we don't swallow exceptions; would rather fail early
-            try {
-                if(numParams == 0) {
-                    MethodExtensions.invoke(method, service);
-                } else {
-                    MethodExtensions.invoke(method, service, new Object[] { props });
-                }
-            } catch(Exception ex) {
-                log.error(String.format(
-                        "@PostConstruct on %s#%s: failed",
-                        service.getClass().getName(), method.getName()),
-                        ex);
-                if(firstExceptionIfAny == null) {
-                    firstExceptionIfAny = ex;
-                }
-            }
-        }
-        if(firstExceptionIfAny != null) {
-            throw new RuntimeException(firstExceptionIfAny);
-        }
-    }
-
-
-
-    // -- preDestroy
-
-    public void preDestroy() {
-        if(log.isInfoEnabled()) {
-            log.info("calling @PreDestroy on all domain services");
-        }
-        for (final Map.Entry<Object, Method> entry : preDestroyMethodsByService.entrySet()) {
-            final Object service = entry.getKey();
-            final Method method = entry.getValue();
-
-            if(log.isDebugEnabled()) {
-                log.debug("... calling @PreDestroy method: {}: {}", service.getClass().getName(), method.getName());
-            }
-
-            try {
-                MethodExtensions.invoke(method, service);
-            } catch(Exception ex) {
-                // do nothing
-                log.warn(String.format(
-                        "@PreDestroy on %s#%s: failed, continuing anyway",
-                        service.getClass().getName(), method.getName()));
-            }
-        }
-    }
-
-
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoader.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoader.java
index 0893c94..867f8f4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoader.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoader.java
@@ -24,34 +24,43 @@ import javax.annotation.Nullable;
 
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.metamodel.commons.ClassUtil;
+import org.apache.isis.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.metamodel.spec.ObjectSpecId;
 import org.apache.isis.metamodel.spec.ObjectSpecification;
 import org.apache.isis.metamodel.specloader.specimpl.IntrospectionState;
 
+import lombok.val;
+
 /**
- * Builds the meta-model.
- * TODO [2033] add missing java-doc
- *
+ * Builds the meta-model, utilizing an instance of {@link ProgrammingModel}
  */
 public interface SpecificationLoader {
-
-    // -- LIVE CYCLE
-
-    void init();
-
-    void shutdown();
+    
+    /**
+     * Creates the meta-model, that is the set of {@link ObjectSpecification}s.
+     * @see {@link #disposeMetaModel()}
+     */
+    void createMetaModel();
+    
+    /**
+     * Clears all instance references to {@link ObjectSpecification}s.
+     * @see {@link #createMetaModel()}
+     */
+    void disposeMetaModel();
+    
+    void validateMetaModel();
 
     // -- LOOKUP
 
     /**
      * @ThreadSafe
      * <p>
-     *     Must be implemented thread-safe to avoid concurrent modification exceptions for if the caller
+     *     Must be implemented thread-safe to avoid concurrent modification exceptions when the caller
      *     iterates over all the specifications and performs an activity that might give rise to new
-     *     ObjectSpec's being discovered, eg performing metamodel validation.
+     *     ObjectSpec's being discovered, eg. performing meta-model validation.
      * </p>
      * 
-     * @return (defensive-copy) snapshot of all the (currently) loaded specifications
+     * @return snapshot of all the (currently) loaded specifications, a defensive-copy 
      */
     Collection<ObjectSpecification> snapshotSpecifications();
 
@@ -61,14 +70,6 @@ public interface SpecificationLoader {
      */
     ObjectSpecification lookupBySpecIdElseLoad(ObjectSpecId objectSpecId);
 
-    //	default ObjectSpecification lookupBySpecIdElseLoad(ObjectSpecId objectSpecId) {
-    //	    return lookupBySpecIdO(objectSpecId).orElseThrow(
-    //	            ()->_Exceptions.unrecoverable(
-    //	                    "Failed to lookup ObjectSpecification by its id '" + objectSpecId + "'"));
-    //	}
-
-    //ValidationFailures validate();
-
     void reloadSpecification(Class<?> domainType);
 
     /**
@@ -83,31 +84,12 @@ public interface SpecificationLoader {
      */
     ObjectSpecification loadSpecification(@Nullable Class<?> domainType, IntrospectionState upTo);
 
-    //	default ObjectSpecification loadSpecification(@Nullable ObjectSpecId objectSpecId, IntrospectionState upTo) {
-    //		if(objectSpecId==null) {
-    //			return null;
-    //		}
-    //		
-    //		val className = objectSpecId.asString();
-    //		
-    //		if(_Strings.isNullOrEmpty(className)) {
-    //			return null;
-    //		}
-    //		
-    //		final Class<?> type = ClassUtil.forNameElseFail(className);
-    //		return loadSpecification(type, upTo);
-    //	}
-
     // -- SHORTCUTS
 
     default ObjectSpecification loadSpecification(@Nullable final Class<?> domainType) {
         return loadSpecification(domainType, IntrospectionState.TYPE_INTROSPECTED);
     }
 
-    //	default ObjectSpecification loadSpecification(@Nullable ObjectSpecId objectSpecId) {
-    //		return loadSpecification(objectSpecId.asString(), IntrospectionState.TYPE_INTROSPECTED);
-    //    }
-
     default ObjectSpecification loadSpecification(
             @Nullable String className, 
             @Nullable IntrospectionState introspectionState) {
@@ -115,7 +97,7 @@ public interface SpecificationLoader {
         if(_Strings.isNullOrEmpty(className)) {
             return null;
         }
-        final Class<?> type = ClassUtil.forNameElseFail(className);
+        val type = ClassUtil.forNameElseFail(className);
         return introspectionState!=null 
                 ? loadSpecification(type, introspectionState)
                         : loadSpecification(type);
@@ -125,6 +107,8 @@ public interface SpecificationLoader {
         return loadSpecification(className, null);
     }
 
+    
+
 
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoaderDefault.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoaderDefault.java
index dd51833..243eff7 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoaderDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/SpecificationLoaderDefault.java
@@ -20,19 +20,17 @@ package org.apache.isis.metamodel.specloader;
 
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 import javax.annotation.Nullable;
 import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 
 import org.springframework.stereotype.Service;
 
-import org.apache.isis.commons.internal.base._Blackhole;
 import org.apache.isis.commons.internal.base._Timing;
 import org.apache.isis.commons.internal.collections._Lists;
-import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.commons.internal.environment.IsisSystemEnvironment;
 import org.apache.isis.config.IsisConfiguration;
 import org.apache.isis.config.registry.IsisBeanTypeRegistry;
@@ -50,6 +48,7 @@ import org.apache.isis.metamodel.specloader.specimpl.FacetedMethodsBuilderContex
 import org.apache.isis.metamodel.specloader.specimpl.IntrospectionState;
 import org.apache.isis.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
 import org.apache.isis.metamodel.specloader.specimpl.standalonelist.ObjectSpecificationOnStandaloneList;
+import org.apache.isis.metamodel.specloader.validator.ValidationFailures;
 import org.apache.isis.schema.utils.CommonDtoUtils;
 
 import static org.apache.isis.commons.internal.base._With.requires;
@@ -85,12 +84,6 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
     private final SpecificationCacheDefault<ObjectSpecification> cache = 
             new SpecificationCacheDefault<>();
     
-    @PostConstruct
-    public void preInit() {
-        this.programmingModel = programmingModelService.getProgrammingModel();
-        this.facetProcessor = new FacetProcessor(programmingModel);
-        this.postProcessor = new PostProcessor(programmingModel);
-    }
 
     /** JUnit Test Support */
     public static SpecificationLoaderDefault getInstance (
@@ -110,25 +103,30 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
         return instance;
     }
 
-
-
     // -- LIVE CYCLE
 
+    @PostConstruct
+    public void init() {
+        if (log.isDebugEnabled()) {
+            log.debug("initialising {}", this);
+        }
+        this.programmingModel = programmingModelService.getProgrammingModel();
+        this.facetProcessor = new FacetProcessor(programmingModel);
+        this.postProcessor = new PostProcessor(programmingModel);
+    }
+    
     /**
      * Initializes and wires up, and primes the cache based on any service
      * classes (provided by the {@link IsisBeanTypeRegistry}).
      */
     @Override
-    public void init() {
+    public void createMetaModel() {
         
-        if (log.isDebugEnabled()) {
-            log.debug("initialising {}", this);
-        }
+        log.info("About to creating the Metamodel");
         
-        // initialize subcomponents
+        // initialize subcomponents, only after @PostConstruct has globally completed
         facetProcessor.init();
         postProcessor.init();
-        
 
         // need to completely load services and mixins (synchronously)
         log.info("Loading all specs (up to state of {})", IntrospectionState.NOT_INTROSPECTED);
@@ -197,15 +195,32 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
         }
 
         stopWatch.stop();
-        log.info("init() - took " + stopWatch);
+        log.info("createMetaModel() - took " + stopWatch);
 
     }
+    
+    @Override
+    public void validateMetaModel() {
+        val failures = new ValidationFailures();
+        programmingModel.streamValidators()
+        .forEach(validator->validator.validateInto(failures));
+       // return failures;
+    }
 
     @Override
-    public void shutdown() {
+    public void disposeMetaModel() {
         cache.clear();
-
-        log.info("shutting down {}", this);
+        log.info("Metamodel disposed.");
+    }
+    
+    @PreDestroy
+    public void shutdown() {
+        log.debug("shutting down {}", this);
+        disposeMetaModel();
+        facetProcessor.shutdown();
+        postProcessor.shutdown();
+        postProcessor = null;
+        facetProcessor = null;
     }
 
     /**
@@ -379,57 +394,4 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
     @Inject private IsisConfiguration isisConfiguration;
     @Inject private IsisSystemEnvironment isisSystemEnvironment;
 
-    // -- 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/metamodel/specloader/postprocessor/PostProcessor.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/postprocessor/PostProcessor.java
index 25c5363..304e3f9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/postprocessor/PostProcessor.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/postprocessor/PostProcessor.java
@@ -30,10 +30,9 @@ import lombok.val;
 public class PostProcessor {
 
     private final ProgrammingModel programmingModel;
-    // populated at #init
-    List<ObjectSpecificationPostProcessor> postProcessors;
+    private List<ObjectSpecificationPostProcessor> postProcessors; // populated at #init
 
-    public PostProcessor(final ProgrammingModel programmingModel) {
+    public PostProcessor(ProgrammingModel programmingModel) {
         this.programmingModel = programmingModel;
     }
 
@@ -41,13 +40,16 @@ public class PostProcessor {
         postProcessors = programmingModel.streamPostProcessors().collect(Collectors.toList());
     }
     
-    public void postProcess(final ObjectSpecification objectSpecification) {
+    public void shutdown() {
+        postProcessors.clear();
+    }
+    
+    public void postProcess(ObjectSpecification objectSpecification) {
 
         for (val postProcessor : postProcessors) {
             postProcessor.postProcess(objectSpecification);
         }
 
-
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidator.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidator.java
index fda6611..547cc6f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidator.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidator.java
@@ -19,6 +19,10 @@
 
 package org.apache.isis.metamodel.specloader.validator;
 
+import java.util.function.Consumer;
+
+import org.apache.isis.metamodel.facetapi.FacetHolder;
+
 public interface MetaModelValidator {
 
     /**
@@ -26,6 +30,9 @@ public interface MetaModelValidator {
      *  
      * @param validationFailures
      */
+    //@Deprecated
     void validateInto(ValidationFailures validationFailures);
+    
+    //void validateInto(FacetHolder holder, Consumer<ValidationFailure> onFailure);
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidatorForValidationFailures.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidatorForValidationFailures.java
index 10ce038..f53626a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidatorForValidationFailures.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/validator/MetaModelValidatorForValidationFailures.java
@@ -41,17 +41,17 @@ public class MetaModelValidatorForValidationFailures extends MetaModelValidatorA
         failures.add(identifier, pattern, arguments);
     }
 
-    public Facet addFailure(Facet facet, String message) {
-        if(facet != null) {
-            val holder = (IdentifiedHolder) facet.getFacetHolder();
-            val identifier = holder.getIdentifier();
-            failures.add(identifier, message + " " + identifier.toFullIdentityString());
-        }
-        return facet;
-    }
-
-    public void addFacet(final Facet facet, final String message) {
-        FacetUtil.addFacet(addFailure(facet, message));
-    }
+//    public Facet addFailure(Facet facet, String message) {
+//        if(facet != null) {
+//            val holder = (IdentifiedHolder) facet.getFacetHolder();
+//            val identifier = holder.getIdentifier();
+//            failures.add(identifier, message + " " + identifier.toFullIdentityString());
+//        }
+//        return facet;
+//    }
+
+//    public void addFacet(Facet facet, String message) {
+//        FacetUtil.addFacet(addFailure(facet, message));
+//    }
 
 }
diff --git a/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/ServiceInitializerTest.java b/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/ServiceInitializerTest.java
deleted file mode 100644
index c0b6134..0000000
--- a/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/ServiceInitializerTest.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.metamodel.specloader;
-
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-
-import org.jmock.Expectations;
-import org.jmock.auto.Mock;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import org.apache.isis.commons.internal.collections._Maps;
-import org.apache.isis.config.IsisConfigurationLegacy;
-import org.apache.isis.unittestsupport.jmocking.JUnitRuleMockery2;
-import org.apache.isis.unittestsupport.jmocking.JUnitRuleMockery2.Mode;
-
-import static org.apache.isis.commons.internal.base._NullSafe.stream;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
-public class ServiceInitializerTest {
-
-    @Rule
-    public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(Mode.INTERFACES_ONLY);
-
-    private Map<String,String> props;
-
-    @Mock
-    private IsisConfigurationLegacy configuration;
-    private ServiceInitializer serviceInitializer;
-
-    @Rule
-    public ExpectedException expectedException = ExpectedException.none();
-
-    @Before
-    public void setUp() throws Exception {
-        props = _Maps.newHashMap();
-        context.checking(new Expectations() {
-            {
-                allowing(configuration).copyToMap();
-                will(returnValue(props));
-            }
-        });
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        serviceInitializer = null;
-    }
-
-
-    private static List<Object> listOf(Object... elements) {
-        return stream(elements)
-                .collect(Collectors.toList());
-    }
-
-    public static class DomainServiceWithPostConstruct {
-        boolean called;
-        @PostConstruct
-        public void x() {
-            this.called = true;
-        }
-    }
-
-    public static class DomainServiceWithPostConstructWithProperties {
-        boolean called;
-        Map<String,String> properties;
-        @PostConstruct
-        public void x(Map<String,String> properties) {
-            this.properties = properties;
-            this.called = true;
-        }
-    }
-
-    @Test
-    public void postConstruct() {
-        final DomainServiceWithPostConstruct d1 = new DomainServiceWithPostConstruct();
-        final DomainServiceWithPostConstructWithProperties d2 = new DomainServiceWithPostConstructWithProperties();
-
-        serviceInitializer = new ServiceInitializer(configuration, listOf(d1, d2));
-        serviceInitializer.validate();
-
-        // when
-        serviceInitializer.postConstruct();
-
-        // then
-        assertThat(d1.called, is(true));
-        assertThat(d2.called, is(true));
-        assertThat(d2.properties, is(props));
-    }
-
-
-    public static class DomainServiceWithPreDestroy {
-        boolean called;
-        @PreDestroy
-        public void x() {
-            this.called = true;
-        }
-    }
-
-    @Test
-    public void preDestroy() {
-        final DomainServiceWithPreDestroy d1 = new DomainServiceWithPreDestroy();
-        final DomainServiceWithPreDestroy d2 = new DomainServiceWithPreDestroy();
-
-        serviceInitializer = new ServiceInitializer(configuration, listOf(d1, d2));
-        serviceInitializer.validate();
-
-        // when
-        serviceInitializer.preDestroy();
-
-        // then
-        assertThat(d1.called, is(true));
-        assertThat(d2.called, is(true));
-    }
-
-    public static class DomainServiceWithMultiplePostConstruct {
-        boolean called;
-        @PostConstruct
-        public void x() {
-            this.called = true;
-        }
-        @PostConstruct
-        public void y() {
-            this.called = true;
-        }
-    }
-
-    @Test
-    public void init_when_postConstructMultiple() {
-        final DomainServiceWithMultiplePostConstruct d1 = new DomainServiceWithMultiplePostConstruct();
-        expectedException.expectMessage(
-                containsString(
-                        "Found more than one @PostConstruct method; service is: org.apache.isis.metamodel.specloader.ServiceInitializerTest$DomainServiceWithMultiplePostConstruct, found"));
-        serviceInitializer = new ServiceInitializer(configuration, listOf(d1));
-
-        // when
-        serviceInitializer.validate();
-    }
-
-    public static class DomainServiceWithMultiplePreDestroy {
-        boolean called;
-        @PreDestroy
-        public void x() {
-            this.called = true;
-        }
-        @PreDestroy
-        public void y() {
-            this.called = true;
-        }
-    }
-
-    @Test
-    public void init_when_preDestroyMultiple() {
-        final DomainServiceWithMultiplePreDestroy d1 = new DomainServiceWithMultiplePreDestroy();
-        expectedException.expectMessage(
-                containsString(
-                        "Found more than one @PreDestroy method; service is: org.apache.isis.metamodel.specloader.ServiceInitializerTest$DomainServiceWithMultiplePreDestroy, found"));
-        serviceInitializer = new ServiceInitializer(configuration, listOf(d1));
-
-        // when
-        serviceInitializer.validate();
-    }
-
-    public static class DomainServiceWithPostConstructOneArgWrongType {
-        boolean called;
-        @PostConstruct
-        public void y(Object o) {
-            this.called = true;
-        }
-    }
-
-    @Test
-    public void init_when_postConstructWrongType() {
-        final DomainServiceWithPostConstructOneArgWrongType d1 = new DomainServiceWithPostConstructOneArgWrongType();
-        expectedException.expectMessage(
-                "@PostConstruct method must be no-arg or 1-arg accepting java.util.Map; method is: org.apache.isis.metamodel.specloader.ServiceInitializerTest$DomainServiceWithPostConstructOneArgWrongType#y");
-        serviceInitializer = new ServiceInitializer(configuration, listOf(d1));
-
-        // when
-        serviceInitializer.validate();
-    }
-
-    public static class DomainServiceWithPostConstructTwoArgs {
-        boolean called;
-        @PostConstruct
-        public void y(Map<String,String> p, Object o) {
-            this.called = true;
-        }
-    }
-
-    @Test
-    public void init_when_postConstructWrongArgs() {
-        final DomainServiceWithPostConstructTwoArgs d1 = new DomainServiceWithPostConstructTwoArgs();
-        expectedException.expectMessage(
-                "@PostConstruct method must be no-arg or 1-arg accepting java.util.Map; method is: org.apache.isis.metamodel.specloader.ServiceInitializerTest$DomainServiceWithPostConstructTwoArgs#y");
-        serviceInitializer = new ServiceInitializer(configuration, listOf(d1));
-
-        // when
-        serviceInitializer.validate();
-    }
-
-    public static class DomainServiceWithPreDestroyOneArgs {
-        boolean called;
-        @PreDestroy
-        public void y(Object o) {
-            this.called = true;
-        }
-    }
-
-
-    @Test
-    public void init_when_preDestroyWrongArgs() {
-        final DomainServiceWithPreDestroyOneArgs d1 = new DomainServiceWithPreDestroyOneArgs();
-        expectedException.expectMessage(
-                "@PreDestroy method must be no-arg; method is: org.apache.isis.metamodel.specloader.ServiceInitializerTest$DomainServiceWithPreDestroyOneArgs#y");
-        serviceInitializer = new ServiceInitializer(configuration, listOf(d1));
-
-        // when
-        serviceInitializer.validate();
-    }
-
-
-}
diff --git a/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/SpecificationLoaderTestAbstract.java b/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/SpecificationLoaderTestAbstract.java
index c6cb32f..683c7ba 100644
--- a/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/SpecificationLoaderTestAbstract.java
+++ b/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/SpecificationLoaderTestAbstract.java
@@ -146,18 +146,15 @@ abstract class SpecificationLoaderTestAbstract {
 
         ((ProgrammingModelAbstract)programmingModel).init(new ProgrammingModelInitFilterDefault());
 
-        _Timing.runVerbose("specificationLoader.init()", specificationLoader::init);
-
-        //specificationLoader.init();
+        _Timing.runVerbose("specificationLoader.createMetaModel()", specificationLoader::createMetaModel);
 
         specification = loadSpecification(specificationLoader);
 
-
     }
 
     @AfterEach
     public void tearDown() throws Exception {
-        specificationLoader.shutdown();
+        specificationLoader.disposeMetaModel();
     }
 
     protected abstract ObjectSpecification loadSpecification(SpecificationLoader reflector);
diff --git a/core/plugins/jdo/common/src/main/java/org/apache/isis/jdo/datanucleus/service/JdoPersistenceLifecycleService.java b/core/plugins/jdo/common/src/main/java/org/apache/isis/jdo/datanucleus/service/JdoPersistenceLifecycleService.java
index 9358d5d..8612b1a 100644
--- a/core/plugins/jdo/common/src/main/java/org/apache/isis/jdo/datanucleus/service/JdoPersistenceLifecycleService.java
+++ b/core/plugins/jdo/common/src/main/java/org/apache/isis/jdo/datanucleus/service/JdoPersistenceLifecycleService.java
@@ -19,6 +19,7 @@
 package org.apache.isis.jdo.datanucleus.service;
 
 import javax.annotation.PostConstruct;
+import javax.inject.Inject;
 import javax.inject.Singleton;
 
 import org.springframework.context.annotation.Bean;
@@ -28,15 +29,13 @@ import org.springframework.stereotype.Service;
 import org.apache.isis.commons.internal.context._Context;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.config.registry.IsisBeanTypeRegistry;
+import org.apache.isis.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.runtime.persistence.IsisJdoRuntimePlugin;
 import org.apache.isis.runtime.system.context.session.AppLifecycleEvent;
 import org.apache.isis.runtime.system.context.session.SessionLifecycleEvent;
 import org.apache.isis.runtime.system.persistence.PersistenceSession;
 import org.apache.isis.runtime.system.persistence.PersistenceSessionFactory;
 import org.apache.isis.runtime.system.session.IsisSession;
-import org.apache.isis.runtime.system.session.IsisSessionFactory;
-
-import static org.apache.isis.commons.internal.base._With.requires;
 
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
@@ -44,6 +43,7 @@ import lombok.extern.log4j.Log4j2;
 @Service @Log4j2
 public class JdoPersistenceLifecycleService {
 
+    @Inject private SpecificationLoader specificationLoader;
     private PersistenceSessionFactory persistenceSessionFactory;
 
     @PostConstruct
@@ -115,7 +115,7 @@ public class JdoPersistenceLifecycleService {
         val persistenceSession =
                 persistenceSessionFactory.createPersistenceSession(authenticationSession);
 
-        //TODO [2033] only to support IsisSessionFactoryDefault
+        // to support static call of PersistenceSession.current(PersistenceSession.class)
         _Context.threadLocalPut(PersistenceSession.class, persistenceSession);
 
         persistenceSession.open();
@@ -143,10 +143,6 @@ public class JdoPersistenceLifecycleService {
     }
 
     private void init() {
-        //TODO [2033] specloader should rather be a Spring/CDI managed object
-        val isisSessionFactory = _Context.getElseFail(IsisSessionFactory.class); 
-        val specificationLoader = isisSessionFactory.getSpecificationLoader();
-        requires(specificationLoader, "specificationLoader");
         persistenceSessionFactory.catalogNamedQueries(specificationLoader);
     }
 
diff --git a/core/runtime/src/main/java/org/apache/isis/runtime/system/session/IsisSessionFactoryDefault.java b/core/runtime/src/main/java/org/apache/isis/runtime/system/session/IsisSessionFactoryDefault.java
index e973f18..936c9d7 100644
--- a/core/runtime/src/main/java/org/apache/isis/runtime/system/session/IsisSessionFactoryDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/runtime/system/session/IsisSessionFactoryDefault.java
@@ -85,18 +85,16 @@ public class IsisSessionFactoryDefault implements IsisSessionFactory {
         localeInitializer.initLocale(configuration);
         timeZoneInitializer.initTimeZone(configuration);
 
-        // ... and make IsisSessionFactory available via the IsisContext static for those
-        // places where we cannot yet inject.
-        _Context.putSingleton(IsisSessionFactory.class, this); //TODO[2112] should no longer be required, since Spring manages this instance
-
         runtimeEventService.fireAppPreMetamodel();
 
-        val taskList = _ConcurrentTaskList.named("IsisSessionFactoryDefault Init");
+        val taskList = _ConcurrentTaskList.named("IsisSessionFactoryDefault Init")
         
-        taskList.addRunnable("SpecificationLoader.init()", this::initMetamodel);
-        taskList.addRunnable("ChangesDtoUtils.init()", ChangesDtoUtils::init);
-        taskList.addRunnable("InteractionDtoUtils.init()", InteractionDtoUtils::init);
-        taskList.addRunnable("CommandDtoUtils.init()", CommandDtoUtils::init);
+        .addRunnable("SpecificationLoader::createThenValidateMetaModel", this::createThenValidateMetaModel)
+        .addRunnable("ChangesDtoUtils::init", ChangesDtoUtils::init)
+        .addRunnable("InteractionDtoUtils::init", InteractionDtoUtils::init)
+        .addRunnable("CommandDtoUtils::init", CommandDtoUtils::init)
+        .addRunnable("AuthenticationManager::init", authenticationManager::init)
+        .addRunnable("AuthorizationManager::init", IsisContext.getAuthorizationManager()::init);
 
         taskList.submit(_ConcurrentContext.forkJoin());
         taskList.await();
@@ -105,33 +103,11 @@ public class IsisSessionFactoryDefault implements IsisSessionFactory {
 
     }
 
-    private void initMetamodel() {
-        val authorizationManager = IsisContext.getAuthorizationManager();
-        
-        specificationLoader.init();
-
-        // we need to do this before checking if the metamodel is valid.
-        //
-        // eg ActionChoicesForCollectionParameterFacetFactory metamodel validator requires a runtime...
-        // at o.a.i.core.metamodel.specloader.specimpl.ObjectActionContributee.getServiceAdapter(ObjectActionContributee.java:287)
-        // at o.a.i.core.metamodel.specloader.specimpl.ObjectActionContributee.determineParameters(ObjectActionContributee.java:138)
-        // at o.a.i.core.metamodel.specloader.specimpl.ObjectActionDefault.getParameters(ObjectActionDefault.java:182)
-        // at o.a.i.core.metamodel.facets.actions.action.ActionChoicesForCollectionParameterFacetFactory$1.validate(ActionChoicesForCollectionParameterFacetFactory.java:85)
-        // at o.a.i.core.metamodel.facets.actions.action.ActionChoicesForCollectionParameterFacetFactory$1.visit(ActionChoicesForCollectionParameterFacetFactory.java:76)
-        // at o.a.i.core.metamodel.specloader.validator.MetaModelValidatorVisiting.validate(MetaModelValidatorVisiting.java:47)
-        //
-        // also, required so that can still call isisSessionFactory#doInSession
-        //
-        // eg todoapp has a custom UserSettingsThemeProvider that is called when rendering any page
-        // (including the metamodel invalid page)
-        // at o.a.i.core.runtime.system.session.IsisSessionFactory.doInSession(IsisSessionFactory.java:327)
-        // at todoapp.webapp.UserSettingsThemeProvider.getActiveTheme(UserSettingsThemeProvider.java:36)
-
-        authenticationManager.init();
-        authorizationManager.init();
+    private void createThenValidateMetaModel() {
+        specificationLoader.createMetaModel();
+        specificationLoader.validateMetaModel();
     }
 
-
     @PreDestroy
     public void shutdown() {
         // call might originate from a different thread than main
@@ -142,7 +118,8 @@ public class IsisSessionFactoryDefault implements IsisSessionFactory {
 
         runtimeEventService.fireAppPreDestroy();
         authenticationManager.shutdown();
-        //specificationLoader.shutdown(); //[2112] lifecycle is managed by IoC
+        IsisContext.getAuthorizationManager().shutdown();
+        //specificationLoader.shutdown(); // lifecycle is managed by IoC
     }
 
     // -- 
diff --git a/examples/smoketests/src/test/java/org/apache/isis/testdomain/domainmodel/SpecloaderPerformanceTest.java b/examples/smoketests/src/test/java/org/apache/isis/testdomain/domainmodel/SpecloaderPerformanceTest.java
index 3181ac6..1f2c6c2 100644
--- a/examples/smoketests/src/test/java/org/apache/isis/testdomain/domainmodel/SpecloaderPerformanceTest.java
+++ b/examples/smoketests/src/test/java/org/apache/isis/testdomain/domainmodel/SpecloaderPerformanceTest.java
@@ -52,7 +52,7 @@ import lombok.val;
         })
 @TestPropertySource({
     IsisPresets.DebugValidation,
-    IsisPresets.DebugProgrammingModel,
+    //IsisPresets.DebugProgrammingModel,
     
 })
 @Incubating("not a real test, just for performance tuning")
@@ -81,8 +81,8 @@ class SpecloaderPerformanceTest {
         
             for(int i=0; i<ITERATIONS; ++i) {
                 _Annotations.clearCache();
-                specificationLoader.shutdown();
-                specificationLoader.init();
+                specificationLoader.disposeMetaModel();
+                specificationLoader.createMetaModel();
            
                 if(System.currentTimeMillis() > goodUntilMillis) {
                     fail("timed out");