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/03 14:54:36 UTC

[isis] branch v2 updated: ISIS-2158: spec-loading: another concurrency issue detected and fixed

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

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


The following commit(s) were added to refs/heads/v2 by this push:
     new 6aa8df2  ISIS-2158: spec-loading: another concurrency issue detected and fixed
6aa8df2 is described below

commit 6aa8df2750aaaf8bbee5b08f8db450d6c176e084
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Oct 3 16:54:25 2019 +0200

    ISIS-2158: spec-loading: another concurrency issue detected and fixed
---
 .../commons/internal/collections/_Multimaps.java   |  56 ++++
 .../apache/isis/metamodel/facets/FacetFactory.java |   1 +
 .../metamodel/facets/MethodRemoverConstants.java   |   2 +-
 .../metamodel/specloader/SpecificationLoader.java  |   1 -
 .../specloader/facetprocessor/FacetProcessor.java  | 292 ++++++++++-----------
 .../specimpl/ObjectSpecificationAbstract.java      |  67 +++--
 6 files changed, 243 insertions(+), 176 deletions(-)

diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java b/core/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
index 94681af..475f9e2 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
@@ -21,6 +21,7 @@ package org.apache.isis.commons.internal.collections;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -71,7 +72,26 @@ public class _Multimaps {
          */
         public void putElement(K key, V value);
         
+        /**
+         * Returns the List to which the specified key is mapped,
+         * or a new List if this map contains no mapping for the key.
+         * <p> In the latter case the List is also put onto this map.  
+         * @param key
+         * @return
+         */
         public List<V> getOrElseNew(K key);
+        
+        /**
+         * Returns the List to which the specified key is mapped,
+         * or an immutable empty List if this map contains no mapping for the key.
+         * <p> In the latter case the List is not put onto this map.  
+         * @param key
+         * @return
+         */
+        default public List<V> getOrElseEmpty(K key) {
+            return getOrDefault(key, Collections.emptyList());
+        }
+        
     }
 
     /**
@@ -88,7 +108,25 @@ public class _Multimaps {
          */
         public void putElement(K key, V value);
         
+        /**
+         * Returns the Set to which the specified key is mapped,
+         * or a new Set if this map contains no mapping for the key.
+         * <p> In the latter case the Set is also put onto this map.  
+         * @param key
+         * @return
+         */
         public Set<V> getOrElseNew(K key);
+        
+        /**
+         * Returns the Set to which the specified key is mapped,
+         * or an immutable empty Set if this map contains no mapping for the key.
+         * <p> In the latter case the Set is not put onto this map.  
+         * @param key
+         * @return
+         */
+        default public Set<V> getOrElseEmpty(K key) {
+            return getOrDefault(key, Collections.emptySet());
+        }
     }
 
     /**
@@ -116,7 +154,25 @@ public class _Multimaps {
          */
         public V getElement(K1 key, K2 subkey);
         
+        /**
+         * Returns the Map to which the specified key is mapped,
+         * or a new Map if this map contains no mapping for the key.
+         * <p> In the latter case the Map is also put onto this map.  
+         * @param key
+         * @return
+         */
         public Map<K2, V> getOrElseNew(K1 key);
+        
+        /**
+         * Returns the Map to which the specified key is mapped,
+         * or an immutable empty Map if this map contains no mapping for the key.
+         * <p> In the latter case the Map not is put onto this map.  
+         * @param key
+         * @return
+         */
+        default public Map<K2, V> getOrElseEmpty(K1 key) {
+            return getOrDefault(key, Collections.emptyMap());
+        }
     }
 
     public static <K, V> ListMultimap<K, V> newListMultimap(
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
index c08f5ea..63dbe9f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
@@ -117,6 +117,7 @@ public interface FacetFactory {
      * Used by the Java5 Reflector's <tt>ProgrammingModel</tt> to reduce the
      * number of {@link FacetFactory factory}s that are queried when building up
      * the meta-model.
+     * 
      */
     List<FeatureType> getFeatureTypes();
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/MethodRemoverConstants.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/MethodRemoverConstants.java
index ecac2c4..254ce21 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/MethodRemoverConstants.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/MethodRemoverConstants.java
@@ -27,7 +27,7 @@ import org.apache.isis.metamodel.methodutils.MethodScope;
 
 public class MethodRemoverConstants {
 
-    public static MethodRemover NULL = new MethodRemover() {
+    public static MethodRemover NOOP = new MethodRemover() {
         @Override
         public void removeMethods(final MethodScope methodScope, final String prefix, final Class<?> returnType, final boolean canBeVoid, final int paramCount, Consumer<Method> onRemoval) {
         }
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 9534e44..d19663a 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
@@ -19,7 +19,6 @@
 package org.apache.isis.metamodel.specloader;
 
 import java.util.Collection;
-import java.util.List;
 
 import javax.annotation.Nullable;
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/facetprocessor/FacetProcessor.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/facetprocessor/FacetProcessor.java
index 9d0e5ac..28a04d9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/facetprocessor/FacetProcessor.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/facetprocessor/FacetProcessor.java
@@ -27,8 +27,11 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.isis.applib.services.inject.ServiceInjector;
+import org.apache.isis.commons.internal.base._Lazy;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.collections._Maps;
+import org.apache.isis.commons.internal.collections._Multimaps;
+import org.apache.isis.commons.internal.collections._Multimaps.ListMultimap;
 import org.apache.isis.metamodel.MetaModelContext;
 import org.apache.isis.metamodel.commons.ListExtensions;
 import org.apache.isis.metamodel.facetapi.FacetHolder;
@@ -78,10 +81,9 @@ public class FacetProcessor {
      * Derived from factories that implement
      * {@link MethodPrefixBasedFacetFactory}.
      *
-     * <p>
-     * If <tt>null</tt>, indicates that the cache hasn't been built.
      */
-    private List<String> cachedMethodPrefixes;
+    private final _Lazy<List<String>> methodPrefixes = 
+            _Lazy.threadSafe(this::init_methodPrefixes);
 
     /**
      * All registered {@link FacetFactory factories} that implement
@@ -90,19 +92,16 @@ public class FacetProcessor {
      * <p>
      * Used within {@link #recognizes(Method)}.
      *
-     * <p>
-     * If <tt>null</tt>, indicates that the cache hasn't been built.
      */
-    private List<MethodFilteringFacetFactory> cachedMethodFilteringFactories;
+    private final _Lazy<List<MethodFilteringFacetFactory>> methodFilteringFactories =
+            _Lazy.threadSafe(this::init_methodFilteringFactories);
 
     /**
      * All registered {@link FacetFactory factories} that implement
      * {@link ContributeeMemberFacetFactory}.
-     *
-     * <p>
-     * If <tt>null</tt>, indicates that the cache hasn't been built.
      */
-    private List<ContributeeMemberFacetFactory> cachedContributeeMemberFacetFactories;
+    private final _Lazy<List<ContributeeMemberFacetFactory>> contributeeMemberFacetFactories =
+            _Lazy.threadSafe(this::init_contributeeMemberFacetFactories);
 
     /**
      * All registered {@link FacetFactory factories} that implement
@@ -110,11 +109,9 @@ public class FacetProcessor {
      *
      * <p>
      * Used within {@link #recognizes(Method)}.
-     *
-     * <p>
-     * If <tt>null</tt>, indicates that the cache hasn't been built.
      */
-    private List<PropertyOrCollectionIdentifyingFacetFactory> cachedPropertyOrCollectionIdentifyingFactories;
+    private final _Lazy<List<PropertyOrCollectionIdentifyingFacetFactory>> propertyOrCollectionIdentifyingFactories =
+            _Lazy.threadSafe(this::init_propertyOrCollectionIdentifyingFactories);
 
     /**
      * ObjectFeatureType => List<FacetFactory>
@@ -123,26 +120,33 @@ public class FacetProcessor {
      * Lazily initialized, then cached. The lists remain in the same order that
      * the factories were {@link #registerFactory(FacetFactory) registered}.
      */
-    private Map<FeatureType, List<FacetFactory>> factoryListByFeatureType = null;
-
-    // //////////////////////////////////////////////////
-    // init, shutdown (application scoped)
-    // //////////////////////////////////////////////////
-
+    private final _Lazy<ListMultimap<FeatureType, FacetFactory>> factoryListByFeatureType = 
+            _Lazy.threadSafe(this::init_factoriesByFeatureType);
+    
+    // -- LIFECYCLE
+    
     public void init() {
+        cleanUp(); 
+        
         serviceInjector = MetaModelContext.current().getServiceInjector();
         val facetFactoryList = programmingModel.getList();
         facetFactoryList.forEach(this::registerFactory);
     }
 
     public void shutdown() {
+        cleanUp();
     }
 
-    public void registerFactory(final FacetFactory factory) {
+    private void cleanUp() {
         clearCaches();
+        factories.clear();
+        serviceInjector = null;
+        factoryByFactoryType.clear();
+    }
+    
+    private void registerFactory(FacetFactory factory) {
         factoryByFactoryType.put(factory.getClass(), factory);
         factories.add(factory);
-
         injectDependenciesInto(factory);
     }
 
@@ -150,7 +154,7 @@ public class FacetProcessor {
      * This is <tt>public</tt> so that can be used for <tt>@Facets</tt>
      * processing.
      */
-    public void injectDependenciesInto(final FacetFactory factory) {
+    public void injectDependenciesInto(FacetFactory factory) {
         serviceInjector.injectServicesInto(factory);
     }
 
@@ -163,15 +167,16 @@ public class FacetProcessor {
      * {@link PropertyOrCollectionIdentifyingFacetFactory}s.
      */
     public Set<Method> findAssociationCandidateAccessors(
-            final Collection<Method> methods, 
-            final Set<Method> candidates) {
+            Collection<Method> methods, 
+            Set<Method> candidates) {
+        
+        val factories = propertyOrCollectionIdentifyingFactories.get();
         
-        cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired();
         for (val method : methods) {
             if (method == null) {
                 continue;
             }
-            for (val facetFactory : cachedPropertyOrCollectionIdentifyingFactories) {
+            for (val facetFactory : factories) {
                 if (facetFactory.isPropertyOrCollectionAccessorCandidate(method)) {
                     candidates.add(method);
                 }
@@ -188,9 +193,11 @@ public class FacetProcessor {
      * <p>
      * Intended to be called after {@link #findAndRemovePropertyAccessors(org.apache.isis.metamodel.facetapi.MethodRemover, java.util.List)} once only reference properties remain.
      */
-    public void findAndRemovePropertyAccessors(final MethodRemover methodRemover, final List<Method> methodListToAppendTo) {
-        cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired();
-        for (final PropertyOrCollectionIdentifyingFacetFactory facetFactory : cachedPropertyOrCollectionIdentifyingFactories) {
+    public void findAndRemovePropertyAccessors(
+            MethodRemover methodRemover, 
+            List<Method> methodListToAppendTo) {
+        
+        for (val facetFactory : propertyOrCollectionIdentifyingFactories.get()) {
             facetFactory.findAndRemovePropertyAccessors(methodRemover, methodListToAppendTo);
         }
     }
@@ -203,9 +210,11 @@ public class FacetProcessor {
      * @see PropertyOrCollectionIdentifyingFacetFactory#findAndRemoveCollectionAccessors(MethodRemover,
      *      List)
      */
-    public void findAndRemoveCollectionAccessors(final MethodRemover methodRemover, final List<Method> methodListToAppendTo) {
-        cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired();
-        for (final PropertyOrCollectionIdentifyingFacetFactory facetFactory : cachedPropertyOrCollectionIdentifyingFactories) {
+    public void findAndRemoveCollectionAccessors(
+            MethodRemover methodRemover, 
+            List<Method> methodListToAppendTo) {
+        
+        for (val facetFactory : propertyOrCollectionIdentifyingFactories.get()) {
             facetFactory.findAndRemoveCollectionAccessors(methodRemover, methodListToAppendTo);
         }
     }
@@ -228,17 +237,15 @@ public class FacetProcessor {
      * factory set does the work) is a slight performance optimization for when
      * there are multiple facet factories that search for the same prefix.
      */
-    public boolean recognizes(final Method method) {
-        cacheMethodPrefixesIfRequired();
-        final String methodName = method.getName();
-        for (final String prefix : cachedMethodPrefixes) {
+    public boolean recognizes(Method method) {
+        val methodName = method.getName();
+        for (val prefix : methodPrefixes.get()) {
             if (methodName.startsWith(prefix)) {
                 return true;
             }
         }
 
-        cacheMethodFilteringFacetFactoriesIfRequired();
-        for (final MethodFilteringFacetFactory factory : cachedMethodFilteringFactories) {
+        for (val factory : methodFilteringFactories.get()) {
             if (factory.recognizes(method)) {
                 return true;
             }
@@ -247,9 +254,9 @@ public class FacetProcessor {
         return false;
     }
 
-    public void processObjectSpecId(final Class<?> cls, final FacetHolder facetHolder) {
-        final List<ObjectSpecIdFacetFactory> factoryList = getObjectSpecIfFacetFactoryList();
-        for (final ObjectSpecIdFacetFactory facetFactory : factoryList) {
+    public void processObjectSpecId(Class<?> cls, FacetHolder facetHolder) {
+        val factoryList = getObjectSpecIfFacetFactoryList();
+        for (val facetFactory : factoryList) {
             facetFactory.process(new ProcessObjectSpecIdContext(cls, facetHolder));
         }
     }
@@ -259,14 +266,16 @@ public class FacetProcessor {
     
     private List<ObjectSpecIdFacetFactory> getObjectSpecIfFacetFactoryList() {
         if(objectSpecIfFacetFactoryList == null) {
-            List<ObjectSpecIdFacetFactory> facetFactories = _Lists.newArrayList();
-            final List<FacetFactory> factoryList = getFactoryListByFeatureType(FeatureType.OBJECT);
-            for (final FacetFactory facetFactory : factoryList) {
+            val facetFactories = _Lists.<ObjectSpecIdFacetFactory>newArrayList();
+            
+            factoryListByFeatureType.get().getOrElseEmpty(FeatureType.OBJECT)
+            .forEach(facetFactory->{
                 if (facetFactory instanceof ObjectSpecIdFacetFactory) {
-                    final ObjectSpecIdFacetFactory objectSpecIdFacetFactory = (ObjectSpecIdFacetFactory) facetFactory;
+                    val objectSpecIdFacetFactory = (ObjectSpecIdFacetFactory) facetFactory;
                     facetFactories.add(objectSpecIdFacetFactory);
                 }
-            }
+            });
+            
             objectSpecIfFacetFactoryList = Collections.unmodifiableList(facetFactories);
         }
         return objectSpecIfFacetFactoryList;
@@ -288,13 +297,17 @@ public class FacetProcessor {
      *            - holder to attach facets to.
      */
     public void process(
-            final Class<?> cls,
-            final MethodRemover methodRemover,
-            final FacetHolder facetHolder) {
-        final List<FacetFactory> factoryList = getFactoryListByFeatureType(FeatureType.OBJECT);
-        for (final FacetFactory facetFactory : factoryList) {
-            facetFactory.process(new ProcessClassContext(cls, removerElseNullRemover(methodRemover), facetHolder));
-        }
+            Class<?> cls,
+            MethodRemover methodRemover,
+            FacetHolder facetHolder) {
+        
+        val ctx = new ProcessClassContext(
+                cls, 
+                removerElseNoopRemover(methodRemover), 
+                facetHolder);
+        
+        factoryListByFeatureType.get().getOrElseEmpty(FeatureType.OBJECT)
+        .forEach(facetFactory->facetFactory.process(ctx));
     }
 
 
@@ -319,28 +332,28 @@ public class FacetProcessor {
      *            action, collection etc)
      */
     public void process(
-            final Class<?> cls,
-            final Method method,
-            final MethodRemover methodRemover,
-            final FacetedMethod facetedMethod,
-            final FeatureType featureType) {
-        final List<FacetFactory> factoryList = getFactoryListByFeatureType(featureType);
-        final ProcessMethodContext processMethodContext =
-                new ProcessMethodContext(cls, featureType, method, removerElseNullRemover(methodRemover), facetedMethod);
-        for (final FacetFactory facetFactory : factoryList) {
-            facetFactory.process(processMethodContext);
-        }
+            Class<?> cls,
+            Method method,
+            MethodRemover methodRemover,
+            FacetedMethod facetedMethod,
+            FeatureType featureType) {
+        
+        
+        val processMethodContext =
+                new ProcessMethodContext(cls, featureType, method, removerElseNoopRemover(methodRemover), facetedMethod);
+        
+        factoryListByFeatureType.get().getOrElseEmpty(featureType)
+        .forEach(facetFactory->facetFactory.process(processMethodContext));
     }
 
 
-    public void processMemberOrder(
-            final ObjectMember facetHolder) {
-        cacheContributeeMemberFacetFactoriesIfRequired();
-        final ContributeeMemberFacetFactory.ProcessContributeeMemberContext processMemberContext =
+    public void processMemberOrder(ObjectMember facetHolder) {
+        
+        val processMemberContext =
                 new ContributeeMemberFacetFactory.ProcessContributeeMemberContext(facetHolder);
-        for (final ContributeeMemberFacetFactory facetFactory : cachedContributeeMemberFacetFactories) {
-            facetFactory.process(processMemberContext);
-        }
+        contributeeMemberFacetFactories.get().forEach(facetFactory->
+            facetFactory.process(processMemberContext));
+        
     }
 
     /**
@@ -362,121 +375,100 @@ public class FacetProcessor {
      * @param facetedMethodParameter
      */
     public void processParams(
-            final Class<?> introspectedClass,
-            final Method method,
-            final int paramNum,
-            final MethodRemover methodRemover,
-            final FacetedMethodParameter facetedMethodParameter) {
-        for (FeatureType featureType : FeatureType.PARAMETERS_ONLY) {
+            Class<?> introspectedClass,
+            Method method,
+            int paramNum,
+            MethodRemover methodRemover,
+            FacetedMethodParameter facetedMethodParameter) {
+        
+        for (val featureType : FeatureType.PARAMETERS_ONLY) {
             processParams(introspectedClass, method, paramNum, methodRemover, facetedMethodParameter, featureType);
         }
     }
 
     public void processParams(
-            final Class<?> introspectedClass,
-            final Method method,
-            final int paramNum,
-            final MethodRemover methodRemover,
-            final FacetedMethodParameter facetedMethodParameter,
-            final FeatureType featureType) {
-        final List<FacetFactory> factoryList = getFactoryListByFeatureType(featureType);
-        final ProcessParameterContext processParameterContext =
+            Class<?> introspectedClass,
+            Method method,
+            int paramNum,
+            MethodRemover methodRemover,
+            FacetedMethodParameter facetedMethodParameter,
+            FeatureType featureType) {
+        
+        val processParameterContext =
                 new ProcessParameterContext(introspectedClass, method, paramNum, methodRemover, facetedMethodParameter);
-        for (final FacetFactory facetFactory : factoryList) {
-            facetFactory.processParams(processParameterContext);
-        }
+        
+        factoryListByFeatureType.get().getOrElseEmpty(featureType)
+        .forEach(facetFactory->facetFactory.processParams(processParameterContext));
     }
 
-    private List<FacetFactory> getFactoryListByFeatureType(final FeatureType featureType) {
-        cacheByFeatureTypeIfRequired();
-        List<FacetFactory> list = factoryListByFeatureType.get(featureType);
-        return list != null? list: Collections.<FacetFactory>emptyList();
-    }
 
     private void clearCaches() {
-        factoryListByFeatureType = null;
-        cachedMethodPrefixes = null;
-        cachedMethodFilteringFactories = null;
-        cachedPropertyOrCollectionIdentifyingFactories = null;
+        factoryListByFeatureType.clear();
+        methodPrefixes.clear();
+        methodFilteringFactories.clear();
+        contributeeMemberFacetFactories.clear();
+        propertyOrCollectionIdentifyingFactories.clear();
     }
+    
+    // -- INITIALIZERS
 
-    private synchronized void cacheByFeatureTypeIfRequired() {
-        if (factoryListByFeatureType != null) {
-            return;
-        }
-        factoryListByFeatureType = _Maps.newHashMap();
-        for (final FacetFactory factory : factories) {
-            final List<FeatureType> featureTypes = factory.getFeatureTypes();
-            for (final FeatureType featureType : featureTypes) {
-                final List<FacetFactory> factoryList = getList(factoryListByFeatureType, featureType);
-                factoryList.add(factory);
-            }
+    private ListMultimap<FeatureType, FacetFactory> init_factoriesByFeatureType() {
+        val factoryListByFeatureType = _Multimaps.<FeatureType, FacetFactory>newListMultimap();
+        for (val factory : factories) {
+            factory.getFeatureTypes().forEach(featureType->
+                factoryListByFeatureType.putElement(featureType, factory));
         }
+        return factoryListByFeatureType;
     }
 
-    private synchronized void cacheMethodPrefixesIfRequired() {
-        if (cachedMethodPrefixes != null) {
-            return;
-        }
-        cachedMethodPrefixes = _Lists.newArrayList();
-        for (final FacetFactory facetFactory : factories) {
+    private List<String> init_methodPrefixes() {
+        val cachedMethodPrefixes = _Lists.<String>newArrayList();
+        for (val facetFactory : factories) {
             if (facetFactory instanceof MethodPrefixBasedFacetFactory) {
-                final MethodPrefixBasedFacetFactory methodPrefixBasedFacetFactory = (MethodPrefixBasedFacetFactory) facetFactory;
+                val methodPrefixBasedFacetFactory = (MethodPrefixBasedFacetFactory) facetFactory;
                 ListExtensions.mergeWith(cachedMethodPrefixes, methodPrefixBasedFacetFactory.getPrefixes());
             }
         }
+        return cachedMethodPrefixes;
     }
 
-    private synchronized void cacheMethodFilteringFacetFactoriesIfRequired() {
-        if (cachedMethodFilteringFactories != null) {
-            return;
-        }
-        cachedMethodFilteringFactories = _Lists.newArrayList();
-        for (final FacetFactory factory : factories) {
+    private List<MethodFilteringFacetFactory> init_methodFilteringFactories() {
+        val methodFilteringFactories = _Lists.<MethodFilteringFacetFactory>newArrayList();
+        for (val factory : factories) {
             if (factory instanceof MethodFilteringFacetFactory) {
-                final MethodFilteringFacetFactory methodFilteringFacetFactory = (MethodFilteringFacetFactory) factory;
-                cachedMethodFilteringFactories.add(methodFilteringFacetFactory);
+                val methodFilteringFacetFactory = (MethodFilteringFacetFactory) factory;
+                methodFilteringFactories.add(methodFilteringFacetFactory);
             }
         }
+        return methodFilteringFactories;
     }
 
-    private synchronized void cacheContributeeMemberFacetFactoriesIfRequired() {
-        if (cachedContributeeMemberFacetFactories != null) {
-            return;
-        }
-        cachedContributeeMemberFacetFactories = _Lists.newArrayList();
-        for (final FacetFactory factory : factories) {
+    private List<ContributeeMemberFacetFactory> init_contributeeMemberFacetFactories() {
+        val contributeeMemberFacetFactories = _Lists.<ContributeeMemberFacetFactory>newArrayList();
+        for (val factory : factories) {
             if (factory instanceof ContributeeMemberFacetFactory) {
                 final ContributeeMemberFacetFactory memberOrderingFacetFactory = (ContributeeMemberFacetFactory) factory;
-                cachedContributeeMemberFacetFactories.add(memberOrderingFacetFactory);
+                contributeeMemberFacetFactories.add(memberOrderingFacetFactory);
             }
         }
+        return contributeeMemberFacetFactories;
     }
 
-    private synchronized void cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired() {
-        if (cachedPropertyOrCollectionIdentifyingFactories != null) {
-            return;
-        }
-        cachedPropertyOrCollectionIdentifyingFactories = _Lists.newArrayList();
-        for (FacetFactory factory : factories) {
+    private List<PropertyOrCollectionIdentifyingFacetFactory> init_propertyOrCollectionIdentifyingFactories() {
+        val propertyOrCollectionIdentifyingFactories = _Lists.<PropertyOrCollectionIdentifyingFacetFactory>newArrayList();
+        for (val factory : factories) {
             if (factory instanceof PropertyOrCollectionIdentifyingFacetFactory) {
-                final PropertyOrCollectionIdentifyingFacetFactory identifyingFacetFactory = (PropertyOrCollectionIdentifyingFacetFactory) factory;
-                cachedPropertyOrCollectionIdentifyingFactories.add(identifyingFacetFactory);
+                val identifyingFacetFactory = (PropertyOrCollectionIdentifyingFacetFactory) factory;
+                propertyOrCollectionIdentifyingFactories.add(identifyingFacetFactory);
             }
         }
+        return propertyOrCollectionIdentifyingFactories;
     }
+    
+    // -- HELPER
 
-    private static <K, T> List<T> getList(final Map<K, List<T>> map, final K key) {
-        List<T> list = map.get(key);
-        if (list == null) {
-            list = _Lists.newArrayList();
-            map.put(key, list);
-        }
-        return list;
-    }
-
-    private MethodRemover removerElseNullRemover(final MethodRemover methodRemover) {
-        return methodRemover != null ? methodRemover : MethodRemoverConstants.NULL;
+    private static MethodRemover removerElseNoopRemover(MethodRemover methodRemover) {
+        return methodRemover != null ? methodRemover : MethodRemoverConstants.NOOP;
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index 9c07cf6..f4d397d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -34,12 +34,14 @@ import javax.enterprise.inject.Vetoed;
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.commons.exceptions.UnknownTypeException;
+import org.apache.isis.commons.internal.base._Lazy;
 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.collections._Multimaps;
 import org.apache.isis.commons.internal.collections._Multimaps.ListMultimap;
 import org.apache.isis.commons.internal.collections._Sets;
+import org.apache.isis.commons.internal.collections._Streams;
 import org.apache.isis.commons.internal.ioc.BeanAdapter;
 import org.apache.isis.commons.internal.ioc.BeanSort;
 import org.apache.isis.commons.internal.reflection._Reflect;
@@ -163,6 +165,13 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
             _Multimaps.newConcurrentListMultimap();
 
     private final List<ObjectSpecification> interfaces = _Lists.newArrayList();
+    
+    // defensive immutable lazy copy of interfaces
+    private final _Lazy<List<ObjectSpecification>> unmodifiableInterfaces = 
+            _Lazy.threadSafe(()->Collections.unmodifiableList(new ArrayList<>(interfaces)));
+    
+    
+    
     private final Subclasses directSubclasses = new Subclasses();
     // built lazily
     private Subclasses transitiveSubclasses;
@@ -330,8 +339,11 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
     }
 
     protected void updateInterfaces(final List<ObjectSpecification> interfaces) {
-        this.interfaces.clear();
-        this.interfaces.addAll(interfaces);
+        synchronized(unmodifiableInterfaces) {
+            this.interfaces.clear();
+            this.interfaces.addAll(interfaces);
+            unmodifiableInterfaces.clear();
+        }
     }
 
     private void updateAsSubclassTo(final ObjectSpecification supertypeSpec) {
@@ -525,26 +537,30 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
     @Override
     public <Q extends Facet> Q getFacet(final Class<Q> facetType) {
 
-        // lookup facet holder's facet
-        final Stream<Q> facets1 = _NullSafe.streamNullable(super.getFacet(facetType));
-
-        // lookup all interfaces
-        final Stream<Q> facets2 = _NullSafe.stream(interfaces)
-                .filter(_NullSafe::isPresent) // just in case
-                .map(interfaceSpec->interfaceSpec.getFacet(facetType));
-
-        // search up the inheritance hierarchy
-        final Stream<Q> facets3 = _NullSafe.streamNullable(superclass()) 
-                .map(superSpec->superSpec.getFacet(facetType));
-
-        final Stream<Q> facetsCombined = Stream.concat(Stream.concat(facets1, facets2), facets3);
-
-        final NotANoopFacetFilter<Q> notANoopFacetFilter = new NotANoopFacetFilter<>();
-
-        return facetsCombined
-                .filter(notANoopFacetFilter)
-                .findFirst()
-                .orElse(notANoopFacetFilter.noopFacet);
+        synchronized(unmodifiableInterfaces) {
+        
+            // lookup facet holder's facet
+            val facets1 = _NullSafe.streamNullable(super.getFacet(facetType));
+    
+            // lookup all interfaces
+            val facets2 = _NullSafe.stream(interfaces())
+                    .filter(_NullSafe::isPresent) // just in case
+                    .map(interfaceSpec->interfaceSpec.getFacet(facetType));
+    
+            // search up the inheritance hierarchy
+            val facets3 = _NullSafe.streamNullable(superclass()) 
+                    .map(superSpec->superSpec.getFacet(facetType));
+    
+            val facetsCombined = _Streams.concat(facets1, facets2, facets3);
+    
+            val notANoopFacetFilter = new NotANoopFacetFilter<Q>();
+    
+            return facetsCombined
+                    .filter(notANoopFacetFilter)
+                    .findFirst()
+                    .orElse(notANoopFacetFilter.noopFacet);
+        
+        }
     }
 
     @Vetoed
@@ -608,7 +624,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
 
     @Override
     public Collection<ObjectSpecification> interfaces() {
-        return Collections.unmodifiableList(interfaces);
+        return unmodifiableInterfaces.get();
     }
 
     @Override
@@ -1019,7 +1035,10 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
             final Object servicePojo,
             final List<ObjectAction> contributeeActionsToAppendTo) {
 
-        log.debug("{} : addContributeeActionsIfAny(...); servicePojo class is: {}", this.getFullIdentifier(), servicePojo.getClass().getName());
+        if(log.isDebugEnabled()) {
+            log.debug("{} : addContributeeActionsIfAny(...); servicePojo class is: {}", 
+                    this.getFullIdentifier(), servicePojo.getClass().getName());
+        }
 
         final Class<?> serviceType = servicePojo.getClass();
         final ObjectSpecification specification = getSpecificationLoader().loadSpecification(serviceType,