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 2021/09/18 20:59:32 UTC

[isis] branch master updated: ISIS-2871: some housekeeping around ComponentFactory

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 26e0c72  ISIS-2871: some housekeeping around ComponentFactory
26e0c72 is described below

commit 26e0c722f19fa4ade314352fb73c273eb9b1924f
Author: andi-huber <ah...@apache.org>
AuthorDate: Sat Sep 18 22:59:20 2021 +0200

    ISIS-2871: some housekeeping around ComponentFactory
---
 .../commons/internal/collections/_Multimaps.java   |  4 +
 .../isis/viewer/wicket/ui/ComponentFactory.java    | 26 ++-----
 .../viewer/wicket/ui/ComponentFactoryAbstract.java |  7 --
 .../ui/app/registry/ComponentFactoryRegistry.java  | 23 ++++--
 .../selector/CollectionSelectorHelper.java         | 57 ++++++--------
 .../CollectionContentsAsAjaxTablePanelFactory.java |  4 +-
 .../scalars/markup/MarkupPanelFactories.java       |  8 +-
 ...ollectionContentsSelectorDropdownPanelTest.java | 79 -------------------
 .../ComponentFactoryRegistryDefault.java           | 88 +++++++---------------
 .../ComponentFactoryRegistryDefaultTest.java       | 81 ++++++++++++++++++++
 10 files changed, 161 insertions(+), 216 deletions(-)

diff --git a/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java b/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
index b3dd97c..8620316 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/collections/_Multimaps.java
@@ -98,6 +98,10 @@ public class _Multimaps {
                     .flatMap(Collection::stream);
         }
 
+        default Stream<V> streamElements(final K key) {
+            return getOrElseEmpty(key).stream();
+        }
+
         /**
          * @return optionally the underlying map, based on whether it implements a {@link NavigableMap}
          */
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/ComponentFactory.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/ComponentFactory.java
index f0a9e8b..992cb0e 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/ComponentFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/ComponentFactory.java
@@ -52,31 +52,15 @@ public interface ComponentFactory extends Serializable {
     ComponentType getComponentType();
 
     public enum ApplicationAdvice {
-        APPLIES(true, false),
-        APPLIES_EXCLUSIVELY(true, true),
-        DOES_NOT_APPLY(false, false);
-        private final boolean applies;
-        private final boolean exclusively;
-
-        private ApplicationAdvice(final boolean applies, final boolean exclusively) {
-            this.applies = applies;
-            this.exclusively = exclusively;
-        }
+        APPLIES,
+        DOES_NOT_APPLY;
 
         public boolean applies() {
-            return applies;
-        }
-
-        /**
-         * Whether no other {@link ComponentFactory}s should apply (ie stop
-         * searching for other views).
-         */
-        public boolean exclusively() {
-            return exclusively;
+            return this == APPLIES;
         }
 
-        public static final ApplicationAdvice appliesIf(final boolean b) {
-            return b ? ApplicationAdvice.APPLIES : ApplicationAdvice.DOES_NOT_APPLY;
+        public static final ApplicationAdvice appliesIf(final boolean condition) {
+            return condition ? ApplicationAdvice.APPLIES : ApplicationAdvice.DOES_NOT_APPLY;
         }
     }
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/ComponentFactoryAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/ComponentFactoryAbstract.java
index 01b8e36..bc22caa 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/ComponentFactoryAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/ComponentFactoryAbstract.java
@@ -97,13 +97,6 @@ public abstract class ComponentFactoryAbstract implements ComponentFactory {
         return ApplicationAdvice.appliesIf(b);
     }
 
-    /**
-     * Convenience for subclasses to call from {@link #appliesTo(IModel)}
-     */
-    protected final ApplicationAdvice appliesExclusivelyIf(final boolean b) {
-        return b ? ApplicationAdvice.APPLIES_EXCLUSIVELY : ApplicationAdvice.DOES_NOT_APPLY;
-    }
-
     @Override
     public final Component createComponent(final IModel<?> model) {
         log.debug("creating component {}", getComponentType()::toString);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/app/registry/ComponentFactoryRegistry.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/app/registry/ComponentFactoryRegistry.java
index 2e44d44..2ab8580 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/app/registry/ComponentFactoryRegistry.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/app/registry/ComponentFactoryRegistry.java
@@ -18,8 +18,7 @@
  */
 package org.apache.isis.viewer.wicket.ui.app.registry;
 
-import java.util.Collection;
-import java.util.List;
+import java.util.stream.Stream;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
@@ -37,12 +36,24 @@ import org.apache.isis.viewer.wicket.ui.ComponentFactory;
  */
 public interface ComponentFactoryRegistry {
 
-    List<ComponentFactory> findComponentFactories(ComponentType componentType, IModel<?> model);
+    Stream<ComponentFactory> streamComponentFactories(ComponentType componentType, IModel<?> model);
 
     /**
      * Finds the "best" {@link ComponentFactory} for the viewId.
      */
-    ComponentFactory findComponentFactory(ComponentType componentType, IModel<?> model);
+    default ComponentFactory findComponentFactory(final ComponentType componentType, final IModel<?> model) {
+        return streamComponentFactories(componentType, model)
+            .findFirst()
+            .orElse(null);
+    }
+
+    default ComponentFactory findComponentFactoryElseFail(final ComponentType componentType, final IModel<?> model) {
+        return streamComponentFactories(componentType, model)
+                .findFirst()
+                .orElseThrow(()->new RuntimeException(String.format(
+                        "could not find component for componentType = '%s'; model object is of type %s",
+                        componentType, model.getClass().getName())));
+    }
 
     /**
      * As per
@@ -79,8 +90,4 @@ public interface ComponentFactoryRegistry {
      */
     Component createComponent(ComponentType componentType, String id, IModel<?> model);
 
-    ComponentFactory findComponentFactoryElseFailFast(ComponentType componentType, IModel<?> model);
-
-    Collection<ComponentFactory> listComponentFactories();
-
 }
\ No newline at end of file
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorHelper.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorHelper.java
index 0c36795..c6da268 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorHelper.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorHelper.java
@@ -22,6 +22,7 @@ import java.io.Serializable;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 import org.springframework.lang.Nullable;
 
@@ -44,6 +45,7 @@ import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.
 import org.apache.isis.viewer.wicket.ui.components.collectioncontents.multiple.CollectionContentsMultipleViewsPanelFactory;
 import org.apache.isis.viewer.wicket.ui.components.collectioncontents.unresolved.CollectionContentsHiddenPanelFactory;
 
+import lombok.Getter;
 import lombok.val;
 
 public class CollectionSelectorHelper implements Serializable {
@@ -54,6 +56,7 @@ public class CollectionSelectorHelper implements Serializable {
 
     private final EntityCollectionModel collectionModel;
 
+    @Getter
     private final List<ComponentFactory> componentFactories;
     private final ComponentHintKey componentHintKey;
 
@@ -74,15 +77,24 @@ public class CollectionSelectorHelper implements Serializable {
                 : ComponentHintKey.noop();
     }
 
-    private List<ComponentFactory> locateComponentFactories(ComponentFactoryRegistry componentFactoryRegistry) {
-        final List<ComponentFactory> componentFactories = componentFactoryRegistry.findComponentFactories(ComponentType.COLLECTION_CONTENTS, collectionModel);
-        final List<ComponentFactory> otherFactories = _Lists.filter(componentFactories,
-                (final ComponentFactory input) ->
-        input.getClass() != CollectionContentsMultipleViewsPanelFactory.class);
-        return ordered(otherFactories);
-    }
+    private List<ComponentFactory> locateComponentFactories(final ComponentFactoryRegistry componentFactoryRegistry) {
+
+        final List<ComponentFactory> ajaxFactoriesToEnd = _Lists.newArrayList();
+
+        final List<ComponentFactory> componentFactories = componentFactoryRegistry
+        .streamComponentFactories(ComponentType.COLLECTION_CONTENTS, collectionModel)
+        .filter(componentFactory -> componentFactory.getClass() != CollectionContentsMultipleViewsPanelFactory.class)
+        .filter(componentFactory -> {
+            if(componentFactory instanceof CollectionContentsAsAjaxTablePanelFactory) {
+                ajaxFactoriesToEnd.add(componentFactory);
+                return false;
+            }
+            return true;
+        })
+        .collect(Collectors.toList());
+
+        componentFactories.addAll(ajaxFactoriesToEnd);
 
-    public List<ComponentFactory> getComponentFactories() {
         return componentFactories;
     }
 
@@ -152,36 +164,11 @@ public class CollectionSelectorHelper implements Serializable {
 
     }
 
-    private static List<ComponentFactory> ordered(List<ComponentFactory> componentFactories) {
-        return orderAjaxTableToEnd(componentFactories);
-    }
-
-    static List<ComponentFactory> orderAjaxTableToEnd(List<ComponentFactory> componentFactories) {
-        int ajaxTableIdx = findAjaxTable(componentFactories);
-        if (ajaxTableIdx >= 0) {
-            List<ComponentFactory> orderedFactories = _Lists.newArrayList(componentFactories);
-            ComponentFactory ajaxTableFactory = orderedFactories.remove(ajaxTableIdx);
-            orderedFactories.add(ajaxTableFactory);
-            return orderedFactories;
-        } else {
-            return componentFactories;
-        }
-    }
-
-    private static int findAjaxTable(List<ComponentFactory> componentFactories) {
-        for (int i = 0; i < componentFactories.size(); i++) {
-            if (componentFactories.get(i) instanceof CollectionContentsAsAjaxTablePanelFactory) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
     private static UiHintContainer getUiHintContainer(final Component component) {
         return UiHintContainer.Util.hintContainerOf(component, EntityCollectionModelParented.class);
     }
 
-    private static boolean hasRenderEagerlyFacet(IModel<?> model) {
+    private static boolean hasRenderEagerlyFacet(final IModel<?> model) {
         return toParentedEntityCollectionModel(model)
         .map(EntityCollectionModelParented::getMetaModel)
         .map(CollectionSelectorHelper::isRenderEagerly)
@@ -194,7 +181,7 @@ public class CollectionSelectorHelper implements Serializable {
     }
 
 
-    private static boolean hasDefaultViewFacet(IModel<?> model) {
+    private static boolean hasDefaultViewFacet(final IModel<?> model) {
         val entityCollectionModel = toParentedEntityCollectionModel(model).orElse(null);
         if (entityCollectionModel == null) {
             return false;
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanelFactory.java
index c7b38a5..ba2c649 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanelFactory.java
@@ -32,7 +32,9 @@ import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
 /**
  * {@link ComponentFactory} for {@link CollectionContentsAsAjaxTablePanel}.
  */
-public class CollectionContentsAsAjaxTablePanelFactory extends ComponentFactoryAbstract implements CollectionContentsAsFactory {
+public class CollectionContentsAsAjaxTablePanelFactory
+extends ComponentFactoryAbstract
+implements CollectionContentsAsFactory {
 
     private static final long serialVersionUID = 1L;
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupPanelFactories.java b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupPanelFactories.java
index 3f4e56e..c3677c9 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupPanelFactories.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/scalars/markup/MarkupPanelFactories.java
@@ -52,7 +52,7 @@ public class MarkupPanelFactories {
 
         private final Class<?> valueType;
 
-        public ParentedAbstract(Class<?> valueType) {
+        public ParentedAbstract(final Class<?> valueType) {
             super(ComponentType.SCALAR_NAME_AND_VALUE, ParentedMarkupPanel.class);
             this.valueType = valueType;
         }
@@ -68,10 +68,10 @@ public class MarkupPanelFactories {
             val scalarType = scalarModel.getTypeOfSpecification().getCorrespondingClass();
 
             if(scalarType.equals(valueType)) {
-                return ApplicationAdvice.APPLIES_EXCLUSIVELY;
+                return ApplicationAdvice.APPLIES;
             }
 
-            return appliesIf( valueType.isAssignableFrom(scalarType) );
+            return appliesIf(valueType.isAssignableFrom(scalarType) );
 
         }
 
@@ -91,7 +91,7 @@ public class MarkupPanelFactories {
 
         private final Class<?> valueType;
 
-        public StandaloneAbstract(Class<?> valueType) {
+        public StandaloneAbstract(final Class<?> valueType) {
             super(ComponentType.VALUE, StandaloneMarkupPanel.class);
             this.valueType = valueType;
         }
diff --git a/viewers/wicket/ui/src/test/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionContentsSelectorDropdownPanelTest.java b/viewers/wicket/ui/src/test/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionContentsSelectorDropdownPanelTest.java
deleted file mode 100644
index 89e6da6..0000000
--- a/viewers/wicket/ui/src/test/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionContentsSelectorDropdownPanelTest.java
+++ /dev/null
@@ -1,79 +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.viewer.wicket.ui.components.collection.selector;
-
-import java.util.Arrays;
-import java.util.List;
-
-import org.jmock.auto.Mock;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
-import org.apache.isis.core.metamodel.context.MetaModelContext;
-import org.apache.isis.core.runtime.context.IsisAppCommonContext;
-import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2;
-import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2.Mode;
-import org.apache.isis.viewer.wicket.ui.ComponentFactory;
-import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory;
-
-import lombok.val;
-
-public class CollectionContentsSelectorDropdownPanelTest {
-
-    @Rule public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(Mode.INTERFACES_AND_CLASSES);
-
-    @Mock private ComponentFactory one;
-    @Mock private ComponentFactory two;
-
-    private ComponentFactory ajaxTableComponentFactory;
-    private MetaModelContext metaModelContext;
-
-    @Before
-    public void setUp() throws Exception {
-        
-        metaModelContext = MetaModelContext_forTesting.buildDefault(); 
-        val commonContext = IsisAppCommonContext.of(metaModelContext);
-        
-        ajaxTableComponentFactory = new CollectionContentsAsAjaxTablePanelFactory() {
-            private static final long serialVersionUID = 1L; {
-            setCommonContext(commonContext);
-        }};
-        
-    }
-
-    @Test
-    public void testOrderAjaxTableToEnd() {
-
-        List<ComponentFactory> componentFactories = 
-                Arrays.<ComponentFactory>asList(
-                        one,
-                        ajaxTableComponentFactory, 
-                        two);
-        List<ComponentFactory> orderAjaxTableToEnd = CollectionSelectorHelper.orderAjaxTableToEnd(componentFactories);
-        assertThat(orderAjaxTableToEnd.get(0), is(one));
-        assertThat(orderAjaxTableToEnd.get(1), is(two));
-        assertThat(orderAjaxTableToEnd.get(2), is(ajaxTableComponentFactory));
-    }
-
-}
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefault.java b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefault.java
index 17286d6..cb75faf 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefault.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefault.java
@@ -18,10 +18,8 @@
  */
 package org.apache.isis.viewer.wicket.viewer.registries.components;
 
-import java.util.Collection;
-import java.util.Iterator;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.annotation.PostConstruct;
 import javax.annotation.Priority;
@@ -35,14 +33,13 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
-import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.collections._Multimaps;
 import org.apache.isis.commons.internal.collections._Multimaps.ListMultimap;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.runtime.context.IsisAppCommonContext;
 import org.apache.isis.viewer.common.model.components.ComponentType;
 import org.apache.isis.viewer.wicket.ui.ComponentFactory;
-import org.apache.isis.viewer.wicket.ui.ComponentFactory.ApplicationAdvice;
 import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
 import org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistrar;
 import org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistrar.ComponentFactoryList;
@@ -58,22 +55,21 @@ import lombok.val;
 @Named("isis.viewer.wicket.ComponentFactoryRegistryDefault")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-public class ComponentFactoryRegistryDefault implements ComponentFactoryRegistry {
+public class ComponentFactoryRegistryDefault
+implements ComponentFactoryRegistry {
 
     @Inject private ComponentFactoryRegistrar componentFactoryRegistrar;
     @Inject private MetaModelContext metaModelContext;
 
-    private ListMultimap<ComponentType, ComponentFactory> componentFactoriesByType;
+    private final ListMultimap<ComponentType, ComponentFactory> componentFactoriesByType =
+            _Multimaps.newListMultimap();
 
     @PostConstruct
     public void init() {
-        componentFactoriesByType = _Multimaps.newListMultimap();
         registerComponentFactories(componentFactoryRegistrar);
     }
 
-    // ///////////////////////////////////////////////////////
-    // Registration
-    // ///////////////////////////////////////////////////////
+    // -- REGISTRATION
 
     /**
      * Registers the provided set of component factories.
@@ -108,17 +104,13 @@ public class ComponentFactoryRegistryDefault implements ComponentFactoryRegistry
 
     private void ensureAllComponentTypesRegistered() {
         for (val componentType : ComponentType.values()) {
-            final Collection<ComponentFactory> componentFactories = componentFactoriesByType.getOrElseEmpty(componentType);
-            if (componentFactories.isEmpty()) {
+            if (componentFactoriesByType.getOrElseEmpty(componentType).isEmpty()) {
                 throw new IllegalStateException("No component factories registered for " + componentType);
             }
         }
     }
 
-    // ///////////////////////////////////////////////////////
-    // Public API
-    // ///////////////////////////////////////////////////////
-
+    // -- PUBLIC API
 
     @Override
     public Component addOrReplaceComponent(final MarkupContainer markupContainer, final ComponentType componentType, final IModel<?> model) {
@@ -136,62 +128,36 @@ public class ComponentFactoryRegistryDefault implements ComponentFactoryRegistry
 
     @Override
     public Component createComponent(final ComponentType componentType, final IModel<?> model) {
-        final ComponentFactory componentFactory = findComponentFactoryElseFailFast(componentType, model);
-        final Component component = componentFactory.createComponent(model);
-        return component;
+        return findComponentFactoryElseFail(componentType, model)
+                .createComponent(model);
     }
 
     @Override
     public Component createComponent(final ComponentType componentType, final String id, final IModel<?> model) {
-        final ComponentFactory componentFactory = findComponentFactoryElseFailFast(componentType, model);
-        final Component component = componentFactory.createComponent(id, model);
-        return component;
+        return findComponentFactoryElseFail(componentType, model)
+                .createComponent(id, model);
     }
 
     @Override
-    public List<ComponentFactory> findComponentFactories(final ComponentType componentType, final IModel<?> model) {
-        val componentFactoryList = componentFactoriesByType.get(componentType);
-        val matchingFactories = _Lists.<ComponentFactory>newArrayList();
-        for (val componentFactory : componentFactoryList) {
-            final ApplicationAdvice appliesTo = componentFactory.appliesTo(componentType, model);
-            if (appliesTo.applies()) {
-                matchingFactories.add(componentFactory);
-            }
-            if (appliesTo.exclusively()) {
-                break;
-            }
-        }
-        if (matchingFactories.isEmpty()) {
-            // will just be one
-            matchingFactories.addAll(componentFactoriesByType.get(ComponentType.UNKNOWN));
-        }
-        return matchingFactories;
+    public Stream<ComponentFactory> streamComponentFactories(final ComponentType componentType, final IModel<?> model) {
+        return Stream.concat(
+                componentFactoriesByType.streamElements(componentType)
+                .filter(componentFactory->componentFactory.appliesTo(componentType, model).applies()),
+                componentFactoriesByType.streamElements(ComponentType.UNKNOWN));
     }
 
-    @Override
-    public ComponentFactory findComponentFactory(final ComponentType componentType, final IModel<?> model) {
-        final Collection<ComponentFactory> componentFactories = findComponentFactories(componentType, model);
-        return firstOrNull(componentFactories);
-    }
+    // -- JUNIT SUPPORT
 
-    @Override
-    public ComponentFactory findComponentFactoryElseFailFast(final ComponentType componentType, final IModel<?> model) {
-        final ComponentFactory componentFactory = findComponentFactory(componentType, model);
-        if (componentFactory == null) {
-            throw new RuntimeException(String.format("could not find component for componentType = '%s'; model object is of type %s", componentType, model.getClass().getName()));
-        }
-        return componentFactory;
+    static ComponentFactoryRegistryDefault forTesting(final List<ComponentFactory> componentFactories) {
+        final var factory = new ComponentFactoryRegistryDefault();
+        _NullSafe.stream(componentFactories)
+        .forEach(componentFactory->
+            factory.componentFactoriesByType.putElement(
+                    componentFactory.getComponentType(),
+                    componentFactory));
+        return factory;
     }
 
-    private static <T> T firstOrNull(final Collection<T> collection) {
-        final Iterator<T> iterator = collection.iterator();
-        return iterator.hasNext() ? iterator.next() : null;
-    }
 
-    @Override
-    public Collection<ComponentFactory> listComponentFactories() {
-        return componentFactoriesByType.streamElements()
-                .collect(Collectors.toList());
-    }
 
 }
diff --git a/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefaultTest.java b/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefaultTest.java
new file mode 100644
index 0000000..a419aae
--- /dev/null
+++ b/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/registries/components/ComponentFactoryRegistryDefaultTest.java
@@ -0,0 +1,81 @@
+/*
+ *  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.viewer.wicket.viewer.registries.components;
+
+import java.util.List;
+
+import org.apache.wicket.model.IModel;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import org.apache.isis.viewer.common.model.components.ComponentType;
+import org.apache.isis.viewer.wicket.ui.ComponentFactory;
+import org.apache.isis.viewer.wicket.ui.ComponentFactory.ApplicationAdvice;
+import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
+import org.apache.isis.viewer.wicket.ui.components.collection.selector.CollectionSelectorHelper;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+class ComponentFactoryRegistryDefaultTest {
+
+    private ComponentFactory one;
+    private ComponentFactory two;
+    private ComponentFactory ajaxTableComponentFactory;
+
+    @BeforeEach
+    void setUp() throws Exception {
+
+        ajaxTableComponentFactory = new CollectionContentsAsAjaxTablePanelFactory() {
+            private static final long serialVersionUID = 1L;
+            @Override public ApplicationAdvice appliesTo(final IModel<?> model) {
+                return ApplicationAdvice.APPLIES;
+            }
+        };
+
+        one = Mockito.mock(ComponentFactoryAbstract.class);
+        Mockito.when(one.getComponentType()).thenReturn(ComponentType.COLLECTION_CONTENTS);
+        Mockito.when(one.appliesTo(ComponentType.COLLECTION_CONTENTS, null)).thenReturn(ApplicationAdvice.APPLIES);
+
+        two = Mockito.mock(ComponentFactoryAbstract.class);
+        Mockito.when(two.getComponentType()).thenReturn(ComponentType.COLLECTION_CONTENTS);
+        Mockito.when(two.appliesTo(ComponentType.COLLECTION_CONTENTS, null)).thenReturn(ApplicationAdvice.APPLIES);
+    }
+
+    @Test
+    void testOrderAjaxTableToEnd() {
+
+        final var compRegistry = ComponentFactoryRegistryDefault.forTesting(List.of(
+                one,
+                ajaxTableComponentFactory,
+                two));
+
+        final var orderAjaxTableToEnd = new CollectionSelectorHelper(null, compRegistry)
+                .getComponentFactories();
+
+        assertThat(orderAjaxTableToEnd, Matchers.contains(
+                one,
+                two,
+                ajaxTableComponentFactory));
+
+    }
+
+}