You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2019/12/03 09:42:21 UTC

[isis] 07/08: ISIS-2197: ClassSubstitutor is now Spring-managed.

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

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

commit 31fdc2ec9d50b0b06e23b3ffb31e6a56f47ec83d
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Dec 3 09:39:56 2019 +0000

    ISIS-2197: ClassSubstitutor is now Spring-managed.
---
 .../facets/object/mixin/MixinIntendedAs.java       | 15 +++--
 .../facets/param/name/ParameterNameFacetTest.java  |  7 ++-
 ...jectSpecIdFacetDerivedFromClassNameFactory.java | 11 +++-
 .../progmodel/ProgrammingModelAbstract.java        |  9 +++
 .../dflt/ProgrammingModelFacetsJava8.java          |  8 ++-
 .../classsubstitutor/ClassSubstitutor.java         | 41 ++++++++++++
 .../ClassSubstitutorAbstract.java}                 | 73 +++++++++-------------
 .../classsubstitutor/ClassSubstitutorDefault.java  | 62 ++++++++++++++++++
 .../isis/metamodel/spec/ObjectSpecification.java   |  3 +-
 .../specloader/ProgrammingModelServiceDefault.java |  4 +-
 .../metamodel/specloader/SpecificationLoader.java  |  3 +-
 .../specloader/SpecificationLoaderDefault.java     |  8 +--
 .../specloader/specimpl/FacetedMethodsBuilder.java | 17 ++---
 .../specimpl/dflt/ObjectSpecificationDefault.java  | 16 ++---
 .../ViewModelSemanticCheckingFacetFactoryTest.java |  4 +-
 ...SpecIdFacetDerivedFromClassNameFactoryTest.java |  1 +
 .../SpecificationLoaderTestAbstract.java           | 10 +--
 .../ClassSubstitutorTest_getClass.java             |  4 +-
 .../JdoDiscriminatorAnnotationFacetFactory.java    | 24 ++++---
 19 files changed, 227 insertions(+), 93 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 05de7e6..e3f9c30 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
@@ -20,6 +20,10 @@ package org.apache.isis.metamodel.facets.object.mixin;
 
 import java.lang.reflect.Method;
 
+import org.apache.isis.applib.services.inject.ServiceInjector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Matchers;
 import org.mockito.Mockito;
 
 import org.apache.isis.applib.Identifier;
@@ -39,10 +43,10 @@ import org.apache.isis.metamodel.progmodel.ProgrammingModelInitFilterDefault;
 import org.apache.isis.metamodel.progmodels.dflt.ProgrammingModelFacetsJava8;
 import org.apache.isis.metamodel.services.title.TitleServiceDefault;
 
-import static org.mockito.Mockito.when;
-
 import lombok.val;
 
+import static org.mockito.Mockito.*;
+
 abstract class MixinIntendedAs {
     
     protected ProgrammingModelFacetsJava8 programmingModel;
@@ -50,8 +54,10 @@ abstract class MixinIntendedAs {
 
     protected void setUp() throws Exception {
 
-        programmingModel = new ProgrammingModelFacetsJava8();
-        
+        val mockServiceInjector = Mockito.mock(ServiceInjector.class);
+        when(mockServiceInjector.injectServicesInto(ArgumentMatchers.any())).thenAnswer(i -> i.getArguments()[0]);
+        programmingModel = new ProgrammingModelFacetsJava8(mockServiceInjector);
+
         val mockTranslationService = Mockito.mock(TranslationService.class);
         when(mockTranslationService.getMode()).thenReturn(Mode.DISABLED);
         
@@ -62,6 +68,7 @@ abstract class MixinIntendedAs {
                 .programmingModel(programmingModel)
                 .translationService(mockTranslationService)
                 .titleService(new TitleServiceDefault())
+                .serviceInjector(mockServiceInjector)
                 .build();
         
         ((ProgrammingModelAbstract)programmingModel)
diff --git a/core/detached-tests/src/test/java/org/apache/isis/metamodel/facets/param/name/ParameterNameFacetTest.java b/core/detached-tests/src/test/java/org/apache/isis/metamodel/facets/param/name/ParameterNameFacetTest.java
index 9a2aed9..28c6ae7 100644
--- a/core/detached-tests/src/test/java/org/apache/isis/metamodel/facets/param/name/ParameterNameFacetTest.java
+++ b/core/detached-tests/src/test/java/org/apache/isis/metamodel/facets/param/name/ParameterNameFacetTest.java
@@ -20,6 +20,7 @@ package org.apache.isis.metamodel.facets.param.name;
 
 import java.lang.reflect.Method;
 
+import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -33,6 +34,7 @@ import org.apache.isis.metamodel.facets.all.named.NamedFacet;
 import org.apache.isis.metamodel.progmodel.ProgrammingModelAbstract;
 import org.apache.isis.metamodel.progmodel.ProgrammingModelInitFilterDefault;
 import org.apache.isis.metamodel.progmodels.dflt.ProgrammingModelFacetsJava8;
+import org.mockito.Mockito;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
@@ -49,7 +51,10 @@ public class ParameterNameFacetTest extends AbstractFacetFactoryJUnit4TestCase {
 
     @Before
     public void setUp() throws Exception {
-        programmingModel = new ProgrammingModelFacetsJava8();
+
+        val mockServiceInjector = Mockito.mock(ServiceInjector.class);
+
+        programmingModel = new ProgrammingModelFacetsJava8(mockServiceInjector);
         
         val metaModelContext = MetaModelContext_forTesting.builder().build();
         
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
index da8c887..1032b42 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
@@ -21,6 +21,7 @@ package org.apache.isis.metamodel.facets.object.objectspecid.classname;
 
 import java.util.stream.Stream;
 
+import javax.inject.Inject;
 import javax.xml.bind.annotation.XmlType;
 
 import org.apache.isis.applib.annotation.NatureOfService;
@@ -33,20 +34,23 @@ import org.apache.isis.metamodel.facets.ObjectSpecIdFacetFactory;
 import org.apache.isis.metamodel.facets.object.domainservice.DomainServiceFacet;
 import org.apache.isis.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
 import org.apache.isis.metamodel.progmodel.ProgrammingModel;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutorDefault;
 import org.apache.isis.metamodel.spec.ObjectSpecification;
 import org.apache.isis.metamodel.spec.feature.Contributed;
 import org.apache.isis.metamodel.spec.feature.ObjectAction;
-import org.apache.isis.metamodel.specloader.classsubstitutor.ClassSubstitutor;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutor;
 import org.apache.isis.metamodel.specloader.validator.MetaModelValidator;
 import org.apache.isis.metamodel.specloader.validator.MetaModelValidatorVisiting;
 
+import lombok.Setter;
 import lombok.val;
 
 public class ObjectSpecIdFacetDerivedFromClassNameFactory
 extends FacetFactoryAbstract
 implements MetaModelRefiner, ObjectSpecIdFacetFactory {
 
-    private final ClassSubstitutor classSubstitutor = new ClassSubstitutor();
+    @Inject
+    private ClassSubstitutor classSubstitutor = new ClassSubstitutorDefault(); // default for testing purposes only, overwritten in prod
 
     public ObjectSpecIdFacetDerivedFromClassNameFactory() {
         super(FeatureType.OBJECTS_ONLY);
@@ -61,6 +65,9 @@ implements MetaModelRefiner, ObjectSpecIdFacetFactory {
         }
         final Class<?> cls = processClassContext.getCls();
         final Class<?> substitutedClass = classSubstitutor.getClass(cls);
+        if(substitutedClass == null) {
+            return;
+        }
 
         final ObjectSpecIdFacet objectSpecIdFacet = createObjectSpecIdFacet(facetHolder, substitutedClass);
         FacetUtil.addFacet(objectSpecIdFacet);
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodel/ProgrammingModelAbstract.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodel/ProgrammingModelAbstract.java
index 238b873..8b691d9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodel/ProgrammingModelAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodel/ProgrammingModelAbstract.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
+import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.collections._Multimaps;
 import org.apache.isis.commons.internal.collections._Multimaps.SetMultimap;
@@ -44,10 +45,16 @@ import lombok.val;
 
 public abstract class ProgrammingModelAbstract implements ProgrammingModel {
 
+    private final ServiceInjector serviceInjector;
+
     private List<FacetFactory> unmodifiableFactories;
     private List<MetaModelValidator> unmodifiableValidators;
     private List<ObjectSpecificationPostProcessor> unmodifiablePostProcessors;
 
+    public ProgrammingModelAbstract(final ServiceInjector serviceInjector) {
+        this.serviceInjector = serviceInjector;
+    }
+
     /**
      * Finalizes the factory collection, can not be modified afterwards.
      * @param filter - the final programming model will only contain factories accepted by this filter
@@ -84,6 +91,7 @@ public abstract class ProgrammingModelAbstract implements ProgrammingModel {
             Marker ... markers) {
         
         assertNotInitialized();
+        serviceInjector.injectServicesInto(instance);
         val factoryEntry = ProgrammingModelEntry.of(instance, markers);
         factoryEntriesByOrder.putElement(order, factoryEntry);
     }
@@ -95,6 +103,7 @@ public abstract class ProgrammingModelAbstract implements ProgrammingModel {
             Marker... markers) {
         
         assertNotInitialized();
+        serviceInjector.injectServicesInto(instance);
         val validatorEntry = ProgrammingModelEntry.of(instance, markers);
         validatorEntriesByOrder.putElement(order, validatorEntry);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java
index f500dc9..be2ff1c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java
@@ -17,6 +17,7 @@
 
 package org.apache.isis.metamodel.progmodels.dflt;
 
+import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.metamodel.facets.actions.action.ActionAnnotationFacetFactory;
 import org.apache.isis.metamodel.facets.actions.action.ActionChoicesForCollectionParameterFacetFactory;
 import org.apache.isis.metamodel.facets.actions.defaults.method.ActionDefaultsFacetViaMethodFactory;
@@ -151,9 +152,10 @@ import org.apache.isis.metamodel.services.title.TitlesAndTranslationsValidator;
 
 @SuppressWarnings("deprecation")
 public final class ProgrammingModelFacetsJava8 extends ProgrammingModelAbstract {
-    
-    public ProgrammingModelFacetsJava8() {
-        
+
+    public ProgrammingModelFacetsJava8(ServiceInjector serviceInjector) {
+        super(serviceInjector);
+
         // must be first, so any Facets created can be replaced by other
         // FacetFactorys later.
         addFactory(FacetProcessingOrder.A1_FALLBACK_DEFAULTS, FallbackFacetFactory.class);
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutor.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutor.java
new file mode 100644
index 0000000..0f26fe7
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutor.java
@@ -0,0 +1,41 @@
+/*
+ *  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.services.classsubstitutor;
+
+import java.util.Set;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.commons.internal.base._Blackhole;
+import org.apache.isis.commons.internal.collections._Sets;
+import org.apache.isis.metamodel.commons.ClassUtil;
+import org.apache.isis.metamodel.specloader.classsubstitutor.ProxyEnhanced;
+import org.springframework.stereotype.Component;
+
+import lombok.val;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * Provides capability to translate or ignore classes.
+ */
+public interface ClassSubstitutor {
+
+    Class<?> getClass(Class<?> type);
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/classsubstitutor/ClassSubstitutor.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutorAbstract.java
similarity index 72%
rename from core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/classsubstitutor/ClassSubstitutor.java
rename to core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutorAbstract.java
index 9109e87..6b33b9f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/classsubstitutor/ClassSubstitutor.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutorAbstract.java
@@ -17,7 +17,9 @@
  *  under the License.
  */
 
-package org.apache.isis.metamodel.specloader.classsubstitutor;
+package org.apache.isis.metamodel.services.classsubstitutor;
+
+import lombok.val;
 
 import java.util.Set;
 
@@ -25,46 +27,20 @@ import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.commons.internal.base._Blackhole;
 import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.metamodel.commons.ClassUtil;
+import org.apache.isis.metamodel.specloader.classsubstitutor.ProxyEnhanced;
+import org.springframework.stereotype.Component;
 
-import lombok.val;
-
-/**
- * Provides capability to translate or ignore classes.
- */
-public class ClassSubstitutor {
-
-    // -- constructor
-
-    public ClassSubstitutor() {
-        ignore("org.apache.isis.applib.DomainObjectContainer"); //TODO [ahuber] still required?
-
-        // ignore cglib
-        ignore("net.sf.cglib.proxy.Factory");
-        ignore("net.sf.cglib.proxy.MethodProxy");
-        ignore("net.sf.cglib.proxy.Callback");
-
-        // ignore javassist
-        ignore("javassist.util.proxy.ProxyObject");
-        ignore("javassist.util.proxy.MethodHandler");
-        
-        ignore("org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator");
-        
-        ignorePackage("com.fasterxml.jackson.");
-        ignorePackage("com.google.gson.");
-
-    }
-
-
-    // -- getClass(Class)
+public abstract class ClassSubstitutorAbstract implements ClassSubstitutor {
 
+    @Override
     public Class<?> getClass(final Class<?> cls) {
 
         if (cls == null) {
             return null;
         }
 
-        // ignore datanucleus proxies
-        if(cls.getName().startsWith("org.datanucleus")) {
+        if(proxyPackageNamesToSkip.stream()
+                .anyMatch(packageName -> cls.getName().startsWith(packageName))) {
             return getClass(cls.getSuperclass());
         }
 
@@ -86,15 +62,15 @@ public class ClassSubstitutor {
         if (ClassUtil.directlyImplements(cls, ProxyEnhanced.class)) {
             return getClass(cls.getSuperclass());
         }
-        
+
         try {
             // guard against cannot introspect
-            _Blackhole.consume(cls.getMethods());    
+            _Blackhole.consume(cls.getMethods());
         } catch (Throwable e) {
             classesToIgnore.add(cls);
             return null;
         }
-        
+
         return cls;
     }
 
@@ -103,20 +79,29 @@ public class ClassSubstitutor {
     private final Set<Class<?>> classesToIgnore = _Sets.newConcurrentHashSet();
     private final Set<String> classNamesToIgnore = _Sets.newHashSet();
     private final Set<String> packageNamesToIgnore = _Sets.newHashSet();
+    private final Set<String> proxyPackageNamesToSkip = _Sets.newHashSet();
 
 
     /**
      * For any classes registered as ignored, {@link #getClass(Class)} will
      * return <tt>null</tt>.
      */
-    private void ignore(final String className) {
+    protected void ignoreClass(final String className) {
         classNamesToIgnore.add(className);
     }
-    
-    private void ignorePackage(final String packageName) {
+
+    protected void ignorePackage(final String packageName) {
         packageNamesToIgnore.add(packageName);
     }
 
+    /**
+     * Any classes in these package represent a proxy (eg DN-enhanced bytecode).
+     * We skip the proxy class itself but traverse up to the supertype.
+     */
+    protected void skipProxyPackage(final String packageName) {
+        proxyPackageNamesToSkip.add(packageName);
+    }
+
     private boolean shouldIgnore(final Class<?> cls) {
         if (cls.isArray()) {
             return shouldIgnore(cls.getComponentType());
@@ -128,14 +113,14 @@ public class ClassSubstitutor {
         }
 
         val className = cls.getName();
-        
+
         try{
-            return classesToIgnore.contains(cls) 
+            return classesToIgnore.contains(cls)
                     || classNamesToIgnore.contains(cls.getCanonicalName())
                     || packageNamesToIgnore.stream().anyMatch(className::startsWith)
                     ;
-                    
-        } catch(java.lang.NoClassDefFoundError e) {
+
+        } catch(NoClassDefFoundError e) {
 
             try{
                 if(cls.isAnonymousClass()) {
@@ -143,7 +128,7 @@ public class ClassSubstitutor {
                 } else {
                     return false;
                 }
-            } catch(java.lang.NoClassDefFoundError ex) {
+            } catch(NoClassDefFoundError ex) {
                 return true;
             }
         }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutorDefault.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutorDefault.java
new file mode 100644
index 0000000..7e5638c
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/services/classsubstitutor/ClassSubstitutorDefault.java
@@ -0,0 +1,62 @@
+/*
+ *  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.services.classsubstitutor;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class ClassSubstitutorDefault extends ClassSubstitutorAbstract {
+
+    public ClassSubstitutorDefault() {
+
+        ignoreCglib();
+        ignoreJavassist();
+        ignoreSpringFramework();
+        ignoreJacksonAndGson();
+        skipDataNucleusProxy();
+
+    }
+
+    protected void ignoreCglib() {
+        ignoreClass("net.sf.cglib.proxy.Factory");
+        ignoreClass("net.sf.cglib.proxy.MethodProxy");
+        ignoreClass("net.sf.cglib.proxy.Callback");
+    }
+
+    protected void ignoreJavassist() {
+        ignoreClass("javassist.util.proxy.ProxyObject");
+        ignoreClass("javassist.util.proxy.MethodHandler");
+    }
+
+    protected void ignoreSpringFramework() {
+        ignoreClass("org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator");
+    }
+
+    protected void ignoreJacksonAndGson() {
+        ignorePackage("com.fasterxml.jackson.");
+        ignorePackage("com.google.gson.");
+    }
+
+    protected void skipDataNucleusProxy() {
+        skipProxyPackage("org.datanucleus.");
+    }
+
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/spec/ObjectSpecification.java
index 765be38..4b06c42 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/spec/ObjectSpecification.java
@@ -30,7 +30,6 @@ import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.commons.exceptions.IsisException;
 import org.apache.isis.commons.internal.collections._Streams;
 import org.apache.isis.commons.internal.ioc.BeanSort;
-import org.apache.isis.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.metamodel.consent.Consent;
 import org.apache.isis.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.metamodel.consent.InteractionResult;
@@ -57,7 +56,7 @@ import org.apache.isis.metamodel.spec.feature.ObjectActionContainer;
 import org.apache.isis.metamodel.spec.feature.ObjectAssociationContainer;
 import org.apache.isis.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.metamodel.specloader.SpecificationLoader;
-import org.apache.isis.metamodel.specloader.classsubstitutor.ClassSubstitutor;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutor;
 import org.apache.isis.metamodel.specloader.specimpl.IntrospectionState;
 import org.apache.isis.metamodel.specloader.specimpl.MixedInMember;
 import org.apache.isis.security.authentication.AuthenticationSession;
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/ProgrammingModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/ProgrammingModelServiceDefault.java
index 9c97e08..b1ec9e1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/ProgrammingModelServiceDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/ProgrammingModelServiceDefault.java
@@ -20,6 +20,7 @@ package org.apache.isis.metamodel.specloader;
 
 import javax.inject.Inject;
 
+import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.services.registry.ServiceRegistry;
@@ -44,6 +45,7 @@ public class ProgrammingModelServiceDefault implements ProgrammingModelService {
     
     // -- HELPER
 
+    @Inject private ServiceInjector serviceInjector;
     @Inject private ServiceRegistry serviceRegistry;
     @Inject private ProgrammingModelInitFilter programmingModelInitFilter;
     @Inject private MetaModelContext metaModelContext;
@@ -55,7 +57,7 @@ public class ProgrammingModelServiceDefault implements ProgrammingModelService {
         
         log.info("About to create the ProgrammingModel.");
 
-        val programmingModel = new ProgrammingModelFacetsJava8();
+        val programmingModel = new ProgrammingModelFacetsJava8(serviceInjector);
 
         // from all plugins out there, add their contributed FacetFactories, Validators 
         // and PostProcessors to the programming model
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 d7f1dcf..12b3c04 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
@@ -26,6 +26,7 @@ import javax.annotation.Nullable;
 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.services.classsubstitutor.ClassSubstitutor;
 import org.apache.isis.metamodel.specloader.specimpl.IntrospectionState;
 import org.apache.isis.metamodel.specloader.validator.MetaModelValidator;
 import org.apache.isis.metamodel.specloader.validator.ValidationFailures;
@@ -93,7 +94,7 @@ public interface SpecificationLoader {
      *
      * <p>
      * It is possible for this method to return <tt>null</tt>, for example if
-     * the configured {@link org.apache.isis.metamodel.specloader.classsubstitutor.ClassSubstitutor}
+     * the configured {@link ClassSubstitutor}
      * has filtered out the class.
      * 
      * @return {@code null} if {@code domainType==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 95b4747..d27e63a 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
@@ -41,10 +41,11 @@ import org.apache.isis.metamodel.facetapi.Facet;
 import org.apache.isis.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.metamodel.progmodel.ProgrammingModelService;
 import org.apache.isis.metamodel.progmodels.dflt.ProgrammingModelFacetsJava8;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutorDefault;
 import org.apache.isis.metamodel.spec.FreeStandingList;
 import org.apache.isis.metamodel.spec.ObjectSpecId;
 import org.apache.isis.metamodel.spec.ObjectSpecification;
-import org.apache.isis.metamodel.specloader.classsubstitutor.ClassSubstitutor;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutor;
 import org.apache.isis.metamodel.specloader.facetprocessor.FacetProcessor;
 import org.apache.isis.metamodel.specloader.postprocessor.PostProcessor;
 import org.apache.isis.metamodel.specloader.specimpl.IntrospectionState;
@@ -85,8 +86,7 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
     @Inject private IsisSystemEnvironment isisSystemEnvironment;
     @Inject private ServiceRegistry serviceRegistry;
     @Inject private IsisBeanTypeRegistryHolder isisBeanTypeRegistryHolder;
-    
-    private final ClassSubstitutor classSubstitutor = new ClassSubstitutor();
+    @Inject private ClassSubstitutor classSubstitutor = new ClassSubstitutorDefault();  // default for testing purposes only, overwritten in prod
 
     private ProgrammingModel programmingModel;
     private FacetProcessor facetProcessor;
@@ -358,7 +358,7 @@ public class SpecificationLoaderDefault implements SpecificationLoader {
                     metaModelContext,
                     facetProcessor, 
                     managedBeanNameIfAny, 
-                    postProcessor);
+                    postProcessor, classSubstitutor);
         }
 
         return objectSpec;
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/FacetedMethodsBuilder.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
index 8e8d1d7..7d7ea38 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
@@ -47,7 +47,7 @@ import org.apache.isis.metamodel.facets.object.mixin.MixinFacet;
 import org.apache.isis.metamodel.methodutils.MethodScope;
 import org.apache.isis.metamodel.spec.ObjectSpecification;
 import org.apache.isis.metamodel.specloader.SpecificationLoader;
-import org.apache.isis.metamodel.specloader.classsubstitutor.ClassSubstitutor;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutor;
 import org.apache.isis.metamodel.specloader.facetprocessor.FacetProcessor;
 import org.apache.isis.metamodel.specloader.traverser.TypeExtractorMethodReturn;
 
@@ -135,7 +135,7 @@ public class FacetedMethodsBuilder {
 
     private final FacetProcessor facetProcessor;
 
-    private final ClassSubstitutor classSubstitutor = new ClassSubstitutor();
+    private final ClassSubstitutor classSubstitutor;
 
     private final SpecificationLoader specificationLoader;
 
@@ -148,21 +148,24 @@ public class FacetedMethodsBuilder {
 
     public FacetedMethodsBuilder(
             final ObjectSpecificationAbstract inspectedTypeSpec,
-            final FacetProcessor facetProcessor) {
-        
+            final FacetProcessor facetProcessor,
+            final ClassSubstitutor classSubstitutor) {
+
         if (log.isDebugEnabled()) {
             log.debug("creating JavaIntrospector for {}", inspectedTypeSpec.getFullIdentifier());
         }
-        
+
+        this.facetProcessor = facetProcessor;
+        this.classSubstitutor = classSubstitutor;
+
         val mmContext = inspectedTypeSpec.getMetaModelContext();
 
         this.inspectedTypeSpec = inspectedTypeSpec;
         this.introspectedClass = inspectedTypeSpec.getCorrespondingClass();
-        
+
         val methodsRemaining = introspectedClass.getMethods();
         this.methodRemover = new FacetedMethodsMethodRemover(introspectedClass, methodsRemaining);
 
-        this.facetProcessor = facetProcessor;
         this.specificationLoader = mmContext.getSpecificationLoader();
 
         val isisConfiguration = mmContext.getConfiguration();
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
index 10e46d0..7a6d209 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
@@ -57,7 +57,7 @@ import org.apache.isis.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.metamodel.spec.feature.ObjectActionParameter;
 import org.apache.isis.metamodel.spec.feature.ObjectAssociation;
 import org.apache.isis.metamodel.spec.feature.ObjectMember;
-import org.apache.isis.metamodel.specloader.classsubstitutor.ClassSubstitutor;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutor;
 import org.apache.isis.metamodel.specloader.facetprocessor.FacetProcessor;
 import org.apache.isis.metamodel.specloader.postprocessor.PostProcessor;
 import org.apache.isis.metamodel.specloader.specimpl.FacetedMethodsBuilder;
@@ -75,8 +75,6 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2 
 public class ObjectSpecificationDefault extends ObjectSpecificationAbstract implements FacetHolder {
 
-    private static final ClassSubstitutor classSubstitutor = new ClassSubstitutor();
-
     private static String determineShortName(final Class<?> introspectedClass) {
         final String name = introspectedClass.getName();
         return name.substring(name.lastIndexOf('.') + 1);
@@ -90,7 +88,9 @@ public class ObjectSpecificationDefault extends ObjectSpecificationAbstract impl
     private Map<Method, ObjectMember> membersByMethod = null;
 
     private final FacetedMethodsBuilder facetedMethodsBuilder;
-    
+    private final ClassSubstitutor classSubstitutor;
+
+
     /**
      * available only for managed-beans
      */
@@ -101,13 +101,15 @@ public class ObjectSpecificationDefault extends ObjectSpecificationAbstract impl
             final MetaModelContext metaModelContext,
             final FacetProcessor facetProcessor,
             final String nameIfIsManagedBean,
-            final PostProcessor postProcessor) {
+            final PostProcessor postProcessor,
+            final ClassSubstitutor classSubstitutor) {
         super(correspondingClass, determineShortName(correspondingClass), facetProcessor, postProcessor);
 
         setMetaModelContext(metaModelContext);
-        
+
         this.nameIfIsManagedBean = nameIfIsManagedBean;
-        this.facetedMethodsBuilder = new FacetedMethodsBuilder(this, facetProcessor);
+        this.facetedMethodsBuilder = new FacetedMethodsBuilder(this, facetProcessor, classSubstitutor);
+        this.classSubstitutor = classSubstitutor;
 
         facetProcessor.processObjectSpecId(correspondingClass, this);
         
diff --git a/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/object/ViewModelSemanticCheckingFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/object/ViewModelSemanticCheckingFacetFactoryTest.java
index d735b02..19596dd 100644
--- a/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/object/ViewModelSemanticCheckingFacetFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/object/ViewModelSemanticCheckingFacetFactoryTest.java
@@ -52,7 +52,7 @@ public class ViewModelSemanticCheckingFacetFactoryTest {
     public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES);
 
 
-    @Mock
+    @Mock @JUnitRuleMockery2.Ignoring
     private ServiceInjector mockServicesInjector;
 
     private MetaModelContext metaModelContext;
@@ -60,7 +60,7 @@ public class ViewModelSemanticCheckingFacetFactoryTest {
 
     private ValidationFailures processThenValidate(final Class<?> cls) {
         
-        val programmingModel = new ProgrammingModelAbstract() {};
+        val programmingModel = new ProgrammingModelAbstract(mockServicesInjector) {};
         facetFactory.refineProgrammingModel(programmingModel);
         programmingModel.init(new ProgrammingModelInitFilterDefault(), metaModelContext);
         
diff --git a/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/object/objectspecid/ObjectSpecIdFacetDerivedFromClassNameFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/object/objectspecid/ObjectSpecIdFacetDerivedFromClassNameFactoryTest.java
index 3c546d3..5542649 100644
--- a/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/object/objectspecid/ObjectSpecIdFacetDerivedFromClassNameFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/object/objectspecid/ObjectSpecIdFacetDerivedFromClassNameFactoryTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.metamodel.facets.object.objectspecid;
 
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutorDefault;
 import org.datanucleus.testing.dom.CustomerAsProxiedByDataNucleus;
 import org.junit.Before;
 import org.junit.Test;
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 d9d0dc0..b2d533e 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
@@ -82,8 +82,7 @@ abstract class SpecificationLoaderTestAbstract {
         }
 
         TitleService mockTitleService() {
-            val mock = Mockito.mock(TitleService.class);
-            return mock;
+            return Mockito.mock(TitleService.class);
         }
         
         TranslationService mockTranslationService() {
@@ -92,12 +91,12 @@ abstract class SpecificationLoaderTestAbstract {
             return mock;
         }
 
-        ServiceInjector getServiceInjector() {
+        ServiceInjector mockServiceInjector() {
             return Mockito.mock(ServiceInjector.class);
         }
-        
+
         ProgrammingModel getProgrammingModel() {
-            return new ProgrammingModelFacetsJava8();
+            return new ProgrammingModelFacetsJava8(mockServiceInjector());
         }
         
     }
@@ -130,6 +129,7 @@ abstract class SpecificationLoaderTestAbstract {
                 .authenticationSessionProvider(mockAuthenticationSessionProvider = producers.mockAuthenticationSessionProvider())
                 .singleton(mockMessageService = producers.mockMessageService())
                 .singleton(mockGridService = producers.mockGridService())
+                .serviceInjector(producers.mockServiceInjector())
                 .build();
         
         specificationLoader = metaModelContext.getSpecificationLoader();
diff --git a/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/classsubstitutor/ClassSubstitutorTest_getClass.java b/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/classsubstitutor/ClassSubstitutorTest_getClass.java
index 0513536..818e2e5 100644
--- a/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/classsubstitutor/ClassSubstitutorTest_getClass.java
+++ b/core/metamodel/src/test/java/org/apache/isis/metamodel/specloader/classsubstitutor/ClassSubstitutorTest_getClass.java
@@ -19,6 +19,8 @@
 
 package org.apache.isis.metamodel.specloader.classsubstitutor;
 
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutor;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutorDefault;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -30,7 +32,7 @@ public class ClassSubstitutorTest_getClass {
 
     @Before
     public void setUp() throws Exception {
-        classSubstitutor = new ClassSubstitutor();
+        classSubstitutor = new ClassSubstitutorDefault();
     }
 
     public static class SomeDomainObject {
diff --git a/core/persistence/jdo/common/src/main/java/org/apache/isis/jdo/metamodel/facets/object/discriminator/JdoDiscriminatorAnnotationFacetFactory.java b/core/persistence/jdo/common/src/main/java/org/apache/isis/jdo/metamodel/facets/object/discriminator/JdoDiscriminatorAnnotationFacetFactory.java
index 17d01d8..7c9e7f0 100644
--- a/core/persistence/jdo/common/src/main/java/org/apache/isis/jdo/metamodel/facets/object/discriminator/JdoDiscriminatorAnnotationFacetFactory.java
+++ b/core/persistence/jdo/common/src/main/java/org/apache/isis/jdo/metamodel/facets/object/discriminator/JdoDiscriminatorAnnotationFacetFactory.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.jdo.metamodel.facets.object.discriminator;
 
+import javax.inject.Inject;
 import javax.jdo.annotations.Discriminator;
 
 import org.apache.isis.commons.internal.base._Strings;
@@ -31,13 +32,14 @@ import org.apache.isis.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.metamodel.facets.ObjectSpecIdFacetFactory;
 import org.apache.isis.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
 import org.apache.isis.metamodel.facets.object.objectspecid.classname.ObjectSpecIdFacetDerivedFromClassName;
-import org.apache.isis.metamodel.specloader.classsubstitutor.ClassSubstitutor;
+import org.apache.isis.metamodel.services.classsubstitutor.ClassSubstitutor;
 
 public class JdoDiscriminatorAnnotationFacetFactory
 extends FacetFactoryAbstract
 implements ObjectSpecIdFacetFactory {
 
-    private final ClassSubstitutor classSubstitutor = new ClassSubstitutor();
+    @Inject
+    private ClassSubstitutor classSubstitutor;
 
     public JdoDiscriminatorAnnotationFacetFactory() {
         super(FeatureType.OBJECTS_ONLY);
@@ -59,13 +61,17 @@ implements ObjectSpecIdFacetFactory {
         final FacetHolder facetHolder = processClassContext.getFacetHolder();
 
         final String annotationValue = annotation.value();
-        final ObjectSpecIdFacet facet =
-                !_Strings.isNullOrEmpty(annotationValue)
-                ? new ObjectSpecIdFacetInferredFromJdoDiscriminatorValueAnnotation(
-                        annotationValue, facetHolder)
-                        : new ObjectSpecIdFacetDerivedFromClassName(
-                                classSubstitutor.getClass(cls).getCanonicalName(), facetHolder);
-                        FacetUtil.addFacet(facet);
+        final ObjectSpecIdFacet facet;
+        if (!_Strings.isNullOrEmpty(annotationValue)) {
+            facet = new ObjectSpecIdFacetInferredFromJdoDiscriminatorValueAnnotation(
+                    annotationValue, facetHolder);
+        } else {
+            final Class<?> substitutedClass = classSubstitutor.getClass(cls);
+            facet = substitutedClass != null
+                        ? new ObjectSpecIdFacetDerivedFromClassName(substitutedClass.getCanonicalName(), facetHolder)
+                        : null;
+        }
+        FacetUtil.addFacet(facet);
     }