You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2023/10/12 16:37:54 UTC

[causeway] 01/01: CAUSEWAY-3630: refactors ComponentFactory to not extend Serializable

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

ahuber pushed a commit to branch 3630-wkt.tabular.ex
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 689006ad80d6022787908d5db631803a0f036b96
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Oct 12 18:37:45 2023 +0200

    CAUSEWAY-3630: refactors ComponentFactory to not extend Serializable
---
 extensions/pom.xml                                 |  1 +
 extensions/vw/exceldownload/wicket-ui/pom.xml      | 40 ++-------
 .../viewer/CollectionContentsAsExcelFactory.java   | 56 ++++---------
 .../wkt/ui/viewer/ExcelFileDownloadLink.java       | 96 ----------------------
 .../CalendaredCollectionFactoryAbstract.java       |  2 -
 ...alendarableCollectionAsFullCalendarFactory.java |  2 -
 ...arEventableCollectionAsFullCalendarFactory.java |  2 -
 .../PdfJsViewerPanelComponentFactory.java          |  3 +-
 .../ListeningMarkupPanelFactoriesForWicket.java    |  2 -
 extensions/vw/tabular/applib/pom.xml               | 46 +++++++++++
 .../wicket-ui => tabular/excel}/pom.xml            | 14 ++--
 .../excel/CausewayModuleExtTabularExcel.java}      | 37 +++------
 .../tabular/excel/model}/CellStyleProvider.java    |  2 +-
 .../tabular/excel/model/ExcelFileFactory.java}     | 36 ++++----
 .../tabular/excel/model}/_TimeConversion.java      |  2 +-
 extensions/vw/tabular/pom.xml                      | 56 +++++++++++++
 .../wkt/components/AsciiDocPanelFactoriesWkt.java  |  2 -
 .../wkt/components/MarkdownPanelFactoriesWkt.java  |  2 -
 .../ui/wkt/components/VegaPanelFactoriesWkt.java   |  2 -
 .../commons/applib/src/main/java/module-info.java  |  1 +
 .../tabular/export/CollectionContentsExporter.java | 62 ++++++++++++++
 .../applib/tabular/export/ComponentModel.java      | 58 +++++++++++++
 .../ui/test/ComponentFactoryAbstractTest_init.java |  6 --
 .../viewer/wicket/ui/ComponentFactory.java         | 11 +--
 .../viewer/wicket/ui/ComponentFactoryAbstract.java |  2 -
 .../ui/app/registry/ComponentFactoryKey.java       | 93 +++++++++++++++++++++
 .../ui/components/about/AboutPanelFactory.java     |  3 -
 .../actioninfo/ActionInfoPanelFactory.java         |  2 -
 .../serviceactions/ServiceActionsPanelFactory.java |  2 -
 .../serviceactions/TertiaryMenuPanelFactory.java   |  2 -
 .../actions/ActionParametersFormPanelFactory.java  |  2 -
 .../actions/ActionParametersPanelFactory.java      |  2 -
 .../BookmarkedPagesPanelFactory.java               |  2 -
 .../CollectionPresentationSelectorHelper.java      | 24 +++---
 .../CollectionPresentationSelectorPanel.java       | 46 ++++++-----
 .../ui/components/collection/selector/_Util.java   | 67 +++++----------
 .../CollectionContentsAsAjaxTablePanelFactory.java |  2 -
 .../export/CollectionContentsAsExportFactory.java  | 60 +++++++++-----
 .../CollectionContentsAsIconsPanelFactory.java     |  2 -
 .../CollectionContentsMultipleViewsPanel.java      | 12 +--
 ...ollectionContentsMultipleViewsPanelFactory.java |  2 -
 .../CollectionContentsAsSummaryFactory.java        |  2 -
 .../CollectionContentsHiddenPanelFactory.java      |  5 +-
 .../ui/components/download/FileDownloadLink.java   | 60 ++++++++++++++
 .../empty/EmptyCollectionPanelFactory.java         |  2 -
 .../entity/EntityComponentFactoryAbstract.java     |  2 -
 .../collection/EntityCollectionPanelFactory.java   |  2 -
 .../entity/header/EntityHeaderPanelFactory.java    |  2 -
 .../icontitle/EntityIconAndTitlePanelFactory.java  |  2 -
 .../EntityIconTitleAndCopyLinkPanelFactory.java    |  5 +-
 .../ui/components/footer/FooterPanelFactory.java   |  2 -
 .../ui/components/header/HeaderPanelFactory.java   |  2 -
 .../components/layout/bs/BSGridPanelFactory.java   |  2 -
 .../property/PropertyEditFormPanelFactory.java     |  2 -
 .../property/PropertyEditPanelFactory.java         |  2 -
 .../scalars/ComponentFactoryScalarAbstract.java    |  2 -
 ...ponentFactoryScalarTypeConstrainedAbstract.java |  2 -
 .../scalars/blobclob/CausewayBlobPanelFactory.java |  2 -
 .../scalars/blobclob/CausewayClobPanelFactory.java |  2 -
 .../scalars/bool/BooleanPanelFactory.java          |  2 -
 .../choices/ChoicesSelect2PanelFactory.java        |  2 -
 .../scalars/image/JavaAwtImagePanelFactory.java    |  2 -
 .../scalars/markup/MarkupPanelFactories.java       | 11 +--
 .../scalars/markup/ScalarMarkupPanel.java          | 11 ++-
 .../passwd/CausewayPasswordPanelFactory.java       |  2 -
 .../scalars/string/StringPanelFactory.java         |  2 -
 .../value/fallback/ValueFallbackPanelFactory.java  |  2 -
 .../StandaloneCollectionPanel.java                 |  9 +-
 .../StandaloneCollectionPanelFactory.java          |  2 -
 .../ui/components/tree/TreePanelFactories.java     |  2 -
 .../unknown/UnknownModelPanelFactory.java          |  2 -
 .../value/StandaloneValuePanelFactory.java         |  2 -
 .../voidreturn/VoidReturnPanelFactory.java         |  2 -
 .../ui/components/welcome/WelcomePanelFactory.java |  2 -
 .../EntityLinkSimplePanelFactory.java              |  2 -
 .../apache/causeway/viewer/wicket/ui/util/Wkt.java | 48 ++++++++++-
 .../causeway/viewer/wicket/ui/util/WktModel.java   | 35 ++++----
 .../ComponentFactoryRegistrarDefault.java          | 16 ++--
 78 files changed, 623 insertions(+), 499 deletions(-)

diff --git a/extensions/pom.xml b/extensions/pom.xml
index cebe94ac6d..66163a5ef6 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -121,6 +121,7 @@
 		<module>vw/sse</module>
 		<module>vw/fullcalendar</module>
 		<module>vw/pdfjs</module>
+		<module>vw/tabular</module>
 
 	</modules>
 
diff --git a/extensions/vw/exceldownload/wicket-ui/pom.xml b/extensions/vw/exceldownload/wicket-ui/pom.xml
index 3a124e6113..55a6e83a03 100644
--- a/extensions/vw/exceldownload/wicket-ui/pom.xml
+++ b/extensions/vw/exceldownload/wicket-ui/pom.xml
@@ -36,43 +36,15 @@
 
     <dependencies>
 
-        <dependency>
-            <groupId>org.apache.causeway.viewer</groupId>
-            <artifactId>causeway-viewer-wicket-ui</artifactId>
+		<dependency>
+            <groupId>org.apache.causeway.extensions</groupId>
+            <artifactId>causeway-extensions-tabular-excel</artifactId>
+            <version>3.0.0-SNAPSHOT</version>
         </dependency>
 
         <dependency>
-            <groupId>org.apache.poi</groupId>
-            <artifactId>poi-ooxml</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>xml-apis</groupId>
-                    <artifactId>xml-apis</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>com.fasterxml.woodstox</groupId>
-                    <artifactId>woodstox-core</artifactId>
-                </exclusion>
-                <!-- circumventing convergence issues since maven-enforcer-plugin.version=3.0.0 -->
-                <exclusion>
-                    <groupId>org.codehaus.plexus</groupId>
-                    <artifactId>plexus-utils</artifactId>
-                </exclusion>
-                <!-- circumventing convergence issues since maven-enforcer-plugin.version=3.0.0 -->
-                <exclusion>
-                    <groupId>org.osgi</groupId>
-                    <artifactId>org.osgi.core</artifactId>
-                </exclusion>
-                <!-- already provided via spring-boot-log4j2 -->
-                <exclusion>
-                    <groupId>org.apache.logging.log4j</groupId>
-                    <artifactId>log4j-api</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.poi</groupId>
-            <artifactId>poi-ooxml-lite</artifactId>
+            <groupId>org.apache.causeway.viewer</groupId>
+            <artifactId>causeway-viewer-wicket-ui</artifactId>
         </dependency>
 
     </dependencies>
diff --git a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CollectionContentsAsExcelFactory.java b/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CollectionContentsAsExcelFactory.java
index 05daba541b..98204809f8 100644
--- a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CollectionContentsAsExcelFactory.java
+++ b/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CollectionContentsAsExcelFactory.java
@@ -20,58 +20,42 @@ package org.apache.causeway.extensions.exceldownload.wkt.ui.viewer;
 
 import java.io.File;
 
-import org.apache.wicket.Component;
 import org.apache.wicket.markup.html.link.DownloadLink;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.LoadableDetachableModel;
-import org.apache.wicket.model.Model;
 
-import org.apache.causeway.viewer.commons.model.components.UiComponentType;
-import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModel;
-import org.apache.causeway.viewer.wicket.ui.CollectionContentsAsFactory;
+import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
+import org.apache.causeway.core.metamodel.interactions.managed.nonscalar.DataTableModel;
+import org.apache.causeway.extensions.tabular.excel.model.ExcelFileFactory;
+import org.apache.causeway.viewer.commons.applib.tabular.export.CollectionContentsExporter;
 import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
-import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
 
 /**
  * {@link ComponentFactory} for {@link DownloadLink}.
  *
  * @since 2.0 {@index}
  */
+@Deprecated
 @org.springframework.stereotype.Component
 public class CollectionContentsAsExcelFactory
-extends ComponentFactoryAbstract
-implements CollectionContentsAsFactory {
-
-    private static final long serialVersionUID = 1L;
-
-    private static final String NAME = "excel";
-
-    public CollectionContentsAsExcelFactory() {
-        super(UiComponentType.COLLECTION_CONTENTS_EXPORT, NAME, DownloadLink.class);
-    }
+implements CollectionContentsExporter {
 
     @Override
-    public ApplicationAdvice appliesTo(final IModel<?> model) {
-        if(!(model instanceof EntityCollectionModel)) {
-            return ApplicationAdvice.DOES_NOT_APPLY;
-        }
-        return ApplicationAdvice.APPLIES;
+    public File createExportFile(final DataTableModel dataTableModel) {
+        return new ExcelFileFactory(dataTableModel).get();
     }
 
     @Override
-    public Component createComponent(final String id, final IModel<?> model) {
-        final EntityCollectionModel collectionModel = (EntityCollectionModel) model;
-        return createDownloadLink(id, collectionModel);
+    public CommonMimeType getMimeType() {
+        return CommonMimeType.XLSX;
     }
 
     @Override
-    public IModel<String> getTitleLabel() {
-        return Model.of("Excel Download");
+    public String getTitleLabel() {
+        return "Excel Download";
     }
 
     @Override
-    public IModel<String> getCssClass() {
-        return Model.of("fa-solid fa-file-excel");
+    public String getCssClass() {
+        return "fa-solid fa-file-excel";
     }
 
     @Override
@@ -79,16 +63,4 @@ implements CollectionContentsAsFactory {
         return 2500;
     }
 
-    // -- HELPER
-
-    private DownloadLink createDownloadLink(final String id, final EntityCollectionModel model) {
-        final LoadableDetachableModel<File> fileModel = ExcelFileModel.of(model);
-        final String xlsxFileName = xlsxFileNameFor(model);
-        final DownloadLink link = new ExcelFileDownloadLink(id, fileModel, xlsxFileName);
-        return link;
-    }
-
-    private static String xlsxFileNameFor(final EntityCollectionModel model) {
-        return model.getName().replaceAll(" ", "") + ".xlsx";
-    }
 }
diff --git a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/ExcelFileDownloadLink.java b/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/ExcelFileDownloadLink.java
deleted file mode 100644
index 7f8cb47b4a..0000000000
--- a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/ExcelFileDownloadLink.java
+++ /dev/null
@@ -1,96 +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.causeway.extensions.exceldownload.wkt.ui.viewer;
-
-import java.io.File;
-
-import org.apache.wicket.markup.ComponentTag;
-import org.apache.wicket.markup.html.link.DownloadLink;
-import org.apache.wicket.model.LoadableDetachableModel;
-import org.apache.wicket.request.IRequestCycle;
-import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
-import org.apache.wicket.request.resource.ContentDisposition;
-import org.apache.wicket.util.encoding.UrlEncoder;
-import org.apache.wicket.util.file.Files;
-import org.apache.wicket.util.resource.FileResourceStream;
-import org.apache.wicket.util.resource.IResourceStream;
-
-class ExcelFileDownloadLink extends DownloadLink {
-
-    private static final long serialVersionUID = 1L;
-
-    private final String xlsxFileName;
-
-    public ExcelFileDownloadLink(
-            final String id, final LoadableDetachableModel<File> model, final String xlsxFileName) {
-        super(id, model, xlsxFileName);
-        this.xlsxFileName = xlsxFileName;
-    }
-
-    @Override
-    public void onClick() {
-
-        final File file = getModelObject();
-        if (file == null)
-        {
-            throw new IllegalStateException(getClass().getName() +
-                    " failed to retrieve a File object from model");
-        }
-
-        String fileName = encodedFileName();
-
-        final IResourceStream resourceStream = new FileResourceStream(
-                new org.apache.wicket.util.file.File(file)) {
-
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            public String getContentType() {
-                return "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
-            }
-        };
-
-        getRequestCycle().scheduleRequestHandlerAfterCurrent(
-                new ResourceStreamRequestHandler(resourceStream)
-                {
-                    @Override
-                    public void respond(final IRequestCycle requestCycle)
-                    {
-                        super.respond(requestCycle);
-                        Files.remove(file);
-                    }
-                }.setFileName(fileName)
-                .setContentDisposition(ContentDisposition.ATTACHMENT));
-    }
-
-    @Override
-    protected void onComponentTag(final ComponentTag tag) {
-        super.onComponentTag(tag);
-        tag.put("download", encodedFileName());
-    }
-
-    private String encodedFileName() {
-        return encoded(this.xlsxFileName);
-    }
-
-    private String encoded(final String xlsxFileName) {
-        return UrlEncoder.QUERY_INSTANCE.encode(xlsxFileName, getRequest().getCharset());
-    }
-
-}
\ No newline at end of file
diff --git a/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/CalendaredCollectionFactoryAbstract.java b/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/CalendaredCollectionFactoryAbstract.java
index a149af4105..d7ee7c13b1 100644
--- a/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/CalendaredCollectionFactoryAbstract.java
+++ b/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/CalendaredCollectionFactoryAbstract.java
@@ -33,8 +33,6 @@ extends ComponentFactoryAbstract
 implements
     CollectionContentsAsFactory {
 
-    private static final long serialVersionUID = 1L;
-
     private static final String NAME = "calendar";
 
     private Class<T> type;
diff --git a/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/calendarable/CalendarableCollectionAsFullCalendarFactory.java b/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/calendarable/CalendarableCollectionAsFullCalendarFactory.java
index 38fe23db06..4d15273169 100644
--- a/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/calendarable/CalendarableCollectionAsFullCalendarFactory.java
+++ b/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/calendarable/CalendarableCollectionAsFullCalendarFactory.java
@@ -33,8 +33,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
 public class CalendarableCollectionAsFullCalendarFactory
 extends CalendaredCollectionFactoryAbstract<Calendarable> {
 
-    private static final long serialVersionUID = 1L;
-
     public CalendarableCollectionAsFullCalendarFactory() {
         super(Calendarable.class);
     }
diff --git a/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/calendareventable/CalendarEventableCollectionAsFullCalendarFactory.java b/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/calendareventable/CalendarEventableCollectionAsFullCalendarFactory.java
index 6f6cfed3b0..459dc1e912 100644
--- a/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/calendareventable/CalendarEventableCollectionAsFullCalendarFactory.java
+++ b/extensions/vw/fullcalendar/wicket/ui/src/main/java/org/apache/causeway/extensions/fullcalendar/wkt/ui/viewer/calendareventable/CalendarEventableCollectionAsFullCalendarFactory.java
@@ -33,8 +33,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
 public class CalendarEventableCollectionAsFullCalendarFactory
 extends CalendaredCollectionFactoryAbstract<CalendarEventable> {
 
-    private static final long serialVersionUID = 1L;
-
     public CalendarEventableCollectionAsFullCalendarFactory() {
         super(CalendarEventable.class);
     }
diff --git a/extensions/vw/pdfjs/wicket/ui/src/main/java/org/apache/causeway/extensions/pdfjs/wkt/ui/components/PdfJsViewerPanelComponentFactory.java b/extensions/vw/pdfjs/wicket/ui/src/main/java/org/apache/causeway/extensions/pdfjs/wkt/ui/components/PdfJsViewerPanelComponentFactory.java
index 0522320349..95473c0c5a 100644
--- a/extensions/vw/pdfjs/wicket/ui/src/main/java/org/apache/causeway/extensions/pdfjs/wkt/ui/components/PdfJsViewerPanelComponentFactory.java
+++ b/extensions/vw/pdfjs/wicket/ui/src/main/java/org/apache/causeway/extensions/pdfjs/wkt/ui/components/PdfJsViewerPanelComponentFactory.java
@@ -29,6 +29,7 @@ import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.extensions.pdfjs.metamodel.facet.PdfJsViewerFacet;
 import org.apache.causeway.viewer.commons.model.components.UiComponentType;
 import org.apache.causeway.viewer.wicket.model.models.ScalarModel;
+import org.apache.causeway.viewer.wicket.ui.ComponentFactory.ApplicationAdvice;
 import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
 
 import lombok.val;
@@ -36,8 +37,6 @@ import lombok.val;
 @org.springframework.stereotype.Component
 public class PdfJsViewerPanelComponentFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     @Inject
     public PdfJsViewerPanelComponentFactory() {
         super(UiComponentType.SCALAR_NAME_AND_VALUE, PdfJsViewerPanel.class);
diff --git a/extensions/vw/sse/wicket/src/main/java/org/apache/causeway/extensions/sse/wicket/markup/ListeningMarkupPanelFactoriesForWicket.java b/extensions/vw/sse/wicket/src/main/java/org/apache/causeway/extensions/sse/wicket/markup/ListeningMarkupPanelFactoriesForWicket.java
index 49d981db53..f23b731b15 100644
--- a/extensions/vw/sse/wicket/src/main/java/org/apache/causeway/extensions/sse/wicket/markup/ListeningMarkupPanelFactoriesForWicket.java
+++ b/extensions/vw/sse/wicket/src/main/java/org/apache/causeway/extensions/sse/wicket/markup/ListeningMarkupPanelFactoriesForWicket.java
@@ -42,7 +42,6 @@ public class ListeningMarkupPanelFactoriesForWicket {
 
     @Component
     public static class Parented extends MarkupPanelFactories.ParentedAbstract<Markup> {
-        private static final long serialVersionUID = 1L;
 
         public Parented() {
             super(Markup.class);
@@ -72,7 +71,6 @@ public class ListeningMarkupPanelFactoriesForWicket {
 
     @Component
     public static class Standalone extends MarkupPanelFactories.StandaloneAbstract<Markup> {
-        private static final long serialVersionUID = 1L;
 
         public Standalone() {
             super(Markup.class);
diff --git a/extensions/vw/tabular/applib/pom.xml b/extensions/vw/tabular/applib/pom.xml
new file mode 100644
index 0000000000..de968873cd
--- /dev/null
+++ b/extensions/vw/tabular/applib/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.causeway.extensions</groupId>
+        <artifactId>causeway-extensions-tabular</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>causeway-extensions-tabular-applib</artifactId>
+    <name>Apache Causeway Ext - Tabular (Applib)</name>
+
+    <properties>
+        <jar-plugin.automaticModuleName>org.apache.causeway.extensions.tabular.applib</jar-plugin.automaticModuleName>
+        <git-plugin.propertiesDir>org/apache/causeway/extensions/tabular/applib</git-plugin.propertiesDir>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.causeway.core</groupId>
+            <artifactId>causeway-core-metamodel</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/extensions/vw/exceldownload/wicket-ui/pom.xml b/extensions/vw/tabular/excel/pom.xml
similarity index 86%
copy from extensions/vw/exceldownload/wicket-ui/pom.xml
copy to extensions/vw/tabular/excel/pom.xml
index 3a124e6113..ba9be4bdce 100644
--- a/extensions/vw/exceldownload/wicket-ui/pom.xml
+++ b/extensions/vw/tabular/excel/pom.xml
@@ -22,23 +22,23 @@
 
     <parent>
         <groupId>org.apache.causeway.extensions</groupId>
-        <artifactId>causeway-extensions-exceldownload</artifactId>
+        <artifactId>causeway-extensions-tabular</artifactId>
         <version>3.0.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>causeway-extensions-exceldownload-wicket-ui</artifactId>
-    <name>Apache Causeway Ext - Wicket Viewer - Excel Download (ui)</name>
+    <artifactId>causeway-extensions-tabular-excel</artifactId>
+    <name>Apache Causeway Ext - Tabular (Excel)</name>
 
     <properties>
-        <jar-plugin.automaticModuleName>org.apache.causeway.extensions.exceldownload.wicket.ui</jar-plugin.automaticModuleName>
-        <git-plugin.propertiesDir>org/apache/causeway/extensions/exceldownload/wicket-ui</git-plugin.propertiesDir>
+        <jar-plugin.automaticModuleName>org.apache.causeway.extensions.tabular.excel</jar-plugin.automaticModuleName>
+        <git-plugin.propertiesDir>org/apache/causeway/extensions/tabular/excel</git-plugin.propertiesDir>
     </properties>
 
     <dependencies>
 
         <dependency>
-            <groupId>org.apache.causeway.viewer</groupId>
-            <artifactId>causeway-viewer-wicket-ui</artifactId>
+            <groupId>org.apache.causeway.extensions</groupId>
+            <artifactId>causeway-extensions-tabular-applib</artifactId>
         </dependency>
 
         <dependency>
diff --git a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/CausewayModuleExtTabularExcel.java
similarity index 51%
copy from extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java
copy to extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/CausewayModuleExtTabularExcel.java
index 506c259714..04fb9b74e9 100644
--- a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java
+++ b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/CausewayModuleExtTabularExcel.java
@@ -16,34 +16,17 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.extensions.exceldownload.wkt.ui.viewer;
+package org.apache.causeway.extensions.tabular.excel;
 
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.time.ZoneId;
-import java.util.Date;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
 
-final class _TimeConversion {
-
-    public static Date toDate(LocalDate value) {
-        return toDate(value.atStartOfDay());
-    }
-
-    public static Date toDate(LocalDateTime value) {
-        return new Date(toEpochMilli(value));
-    }
-
-    public static Date toDate(OffsetDateTime value) {
-        return toDate(value.toLocalDateTime());
-    }
-
-    // -- HELPER
-
-    private static final ZoneId zId = ZoneId.systemDefault();
-
-    private static long toEpochMilli(LocalDateTime localDateTime){
-        return localDateTime.atZone(zId).toInstant().toEpochMilli();
-    }
+/**
+ * @since 2.0 {@index}
+ */
+@Configuration
+@Import({
 
+})
+public class CausewayModuleExtTabularExcel {
 }
diff --git a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CellStyleProvider.java b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/CellStyleProvider.java
similarity index 98%
rename from extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CellStyleProvider.java
rename to extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/CellStyleProvider.java
index 16d292fa2a..a2c3f50e1f 100644
--- a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CellStyleProvider.java
+++ b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/CellStyleProvider.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.extensions.exceldownload.wkt.ui.viewer;
+package org.apache.causeway.extensions.tabular.excel.model;
 
 import java.awt.Color;
 
diff --git a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/ExcelFileModel.java b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/ExcelFileFactory.java
similarity index 91%
rename from extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/ExcelFileModel.java
rename to extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/ExcelFileFactory.java
index 59d06800c8..e62707dcb0 100644
--- a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/ExcelFileModel.java
+++ b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/ExcelFileFactory.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.extensions.exceldownload.wkt.ui.viewer;
+package org.apache.causeway.extensions.tabular.excel.model;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -28,6 +28,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
 import java.util.Date;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -37,7 +38,6 @@ import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
-import org.apache.wicket.model.LoadableDetachableModel;
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.commons.collections.Can;
@@ -45,15 +45,14 @@ import org.apache.causeway.commons.internal.base._Reduction;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.core.metamodel.interactions.managed.nonscalar.DataTableModel;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
-import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModel;
 
+import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import lombok.SneakyThrows;
 import lombok.val;
+import lombok.experimental.Accessors;
 
-class ExcelFileModel extends LoadableDetachableModel<File> {
-
-    private static final long serialVersionUID = 1L;
+public class ExcelFileFactory implements Supplier<File> {
 
     /**
      * If a cell's cardinality exceeds this threshold, truncate with '... has more' label at the end.
@@ -61,14 +60,11 @@ class ExcelFileModel extends LoadableDetachableModel<File> {
     private static final int MAX_CELL_ELEMENTS = 5;
     private static final String POI_LINE_DELIMITER = "\n";
 
-    private final EntityCollectionModel model;
-
-    public static ExcelFileModel of(final EntityCollectionModel model) {
-        return new ExcelFileModel(model);
-    }
+    @Getter @Accessors(fluent=true)
+    private final DataTableModel table;
 
-    private ExcelFileModel(final EntityCollectionModel model) {
-        this.model = model;
+    public ExcelFileFactory(final DataTableModel table) {
+        this.table = table;
     }
 
     @RequiredArgsConstructor
@@ -80,21 +76,17 @@ class ExcelFileModel extends LoadableDetachableModel<File> {
         }
     }
 
-    private DataTableModel table() {
-        return model.getDataTableModel();
-    }
-
     @Override @SneakyThrows
-    protected File load() {
-        return createFile();
+    public File get() {
+        return createTempFile();
     }
 
-    private File createFile() throws IOException, FileNotFoundException {
+    private File createTempFile() throws IOException, FileNotFoundException {
         try(final Workbook wb = new XSSFWorkbook()) {
             final String sheetName = _Strings.nonEmpty(table().getTitle().getValue())
                     .orElse("Collection"); // fallback sheet name
 
-            val tempFile = File.createTempFile(ExcelFileModel.class.getCanonicalName(), sheetName + ".xlsx");
+            val tempFile = File.createTempFile(ExcelFileFactory.class.getCanonicalName(), sheetName + ".xlsx");
             Row row;
 
             try(val fos = new FileOutputStream(tempFile)) {
@@ -102,7 +94,7 @@ class ExcelFileModel extends LoadableDetachableModel<File> {
 
                 val cellStyleProvider = new CellStyleProvider(wb);
 
-                final ExcelFileModel.RowFactory rowFactory = new RowFactory(sheet);
+                final ExcelFileFactory.RowFactory rowFactory = new RowFactory(sheet);
 
 
                 val dataColumns = table().getDataColumns().getValue();
diff --git a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/_TimeConversion.java
similarity index 95%
copy from extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java
copy to extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/_TimeConversion.java
index 506c259714..9db8f5d752 100644
--- a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java
+++ b/extensions/vw/tabular/excel/src/main/java/org/apache/causeway/extensions/tabular/excel/model/_TimeConversion.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.extensions.exceldownload.wkt.ui.viewer;
+package org.apache.causeway.extensions.tabular.excel.model;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
diff --git a/extensions/vw/tabular/pom.xml b/extensions/vw/tabular/pom.xml
new file mode 100644
index 0000000000..0e8c615aad
--- /dev/null
+++ b/extensions/vw/tabular/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.causeway.extensions</groupId>
+        <artifactId>causeway-extensions</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>causeway-extensions-tabular</artifactId>
+    <name>Apache Causeway Ext - Tabular (parent)</name>
+    <description>
+        Basis for viewers to provide alternative representations of collections for download.
+    </description>
+    <packaging>pom</packaging>
+
+    <dependencyManagement>
+		<dependencies>
+            <dependency>
+                <groupId>org.apache.causeway.extensions</groupId>
+                <artifactId>causeway-extensions-tabular-applib</artifactId>
+                <version>3.0.0-SNAPSHOT</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.causeway.extensions</groupId>
+                <artifactId>causeway-extensions-tabular-excel</artifactId>
+                <version>3.0.0-SNAPSHOT</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <modules>
+        <module>excel</module>
+    </modules>
+
+</project>
diff --git a/valuetypes/asciidoc/ui/wicket/src/main/java/org/apache/causeway/valuetypes/asciidoc/ui/wkt/components/AsciiDocPanelFactoriesWkt.java b/valuetypes/asciidoc/ui/wicket/src/main/java/org/apache/causeway/valuetypes/asciidoc/ui/wkt/components/AsciiDocPanelFactoriesWkt.java
index 3028c4d65d..71b5903dab 100644
--- a/valuetypes/asciidoc/ui/wicket/src/main/java/org/apache/causeway/valuetypes/asciidoc/ui/wkt/components/AsciiDocPanelFactoriesWkt.java
+++ b/valuetypes/asciidoc/ui/wicket/src/main/java/org/apache/causeway/valuetypes/asciidoc/ui/wkt/components/AsciiDocPanelFactoriesWkt.java
@@ -40,7 +40,6 @@ public class AsciiDocPanelFactoriesWkt {
 
     @Component
     public static class Parented extends MarkupPanelFactories.ParentedAbstract<AsciiDoc> {
-        private static final long serialVersionUID = 1L;
 
         public Parented() {
             super(AsciiDoc.class);
@@ -59,7 +58,6 @@ public class AsciiDocPanelFactoriesWkt {
 
     @Component
     public static class Standalone extends MarkupPanelFactories.StandaloneAbstract<AsciiDoc> {
-        private static final long serialVersionUID = 1L;
 
         public Standalone() {
             super(AsciiDoc.class);
diff --git a/valuetypes/markdown/ui/wicket/src/main/java/org/apache/causeway/valuetypes/markdown/ui/wkt/components/MarkdownPanelFactoriesWkt.java b/valuetypes/markdown/ui/wicket/src/main/java/org/apache/causeway/valuetypes/markdown/ui/wkt/components/MarkdownPanelFactoriesWkt.java
index 06793e18ab..a0c7d01132 100644
--- a/valuetypes/markdown/ui/wicket/src/main/java/org/apache/causeway/valuetypes/markdown/ui/wkt/components/MarkdownPanelFactoriesWkt.java
+++ b/valuetypes/markdown/ui/wicket/src/main/java/org/apache/causeway/valuetypes/markdown/ui/wkt/components/MarkdownPanelFactoriesWkt.java
@@ -40,7 +40,6 @@ public class MarkdownPanelFactoriesWkt {
 
     @Component
     public static class Parented extends MarkupPanelFactories.ParentedAbstract<Markdown> {
-        private static final long serialVersionUID = 1L;
 
         public Parented() {
             super(Markdown.class);
@@ -59,7 +58,6 @@ public class MarkdownPanelFactoriesWkt {
 
     @Component
     public static class Standalone extends MarkupPanelFactories.StandaloneAbstract<Markdown> {
-        private static final long serialVersionUID = 1L;
 
         public Standalone() {
             super(Markdown.class);
diff --git a/valuetypes/vega/ui/wicket/src/main/java/org/apache/causeway/valuetypes/vega/ui/wkt/components/VegaPanelFactoriesWkt.java b/valuetypes/vega/ui/wicket/src/main/java/org/apache/causeway/valuetypes/vega/ui/wkt/components/VegaPanelFactoriesWkt.java
index 2585d3cbe6..53f2d7bbbe 100644
--- a/valuetypes/vega/ui/wicket/src/main/java/org/apache/causeway/valuetypes/vega/ui/wkt/components/VegaPanelFactoriesWkt.java
+++ b/valuetypes/vega/ui/wicket/src/main/java/org/apache/causeway/valuetypes/vega/ui/wkt/components/VegaPanelFactoriesWkt.java
@@ -39,7 +39,6 @@ public class VegaPanelFactoriesWkt {
 
     @Component
     public static class Parented extends MarkupPanelFactories.ParentedAbstract<Vega> {
-        private static final long serialVersionUID = 1L;
 
         public Parented() {
             super(Vega.class);
@@ -58,7 +57,6 @@ public class VegaPanelFactoriesWkt {
 
     @Component
     public static class Standalone extends MarkupPanelFactories.StandaloneAbstract<Vega> {
-        private static final long serialVersionUID = 1L;
 
         public Standalone() {
             super(Vega.class);
diff --git a/viewers/commons/applib/src/main/java/module-info.java b/viewers/commons/applib/src/main/java/module-info.java
index c0c5de8169..537a2f8602 100644
--- a/viewers/commons/applib/src/main/java/module-info.java
+++ b/viewers/commons/applib/src/main/java/module-info.java
@@ -23,6 +23,7 @@ module org.apache.causeway.viewer.commons.applib {
     exports org.apache.causeway.viewer.commons.applib.services.menu;
     exports org.apache.causeway.viewer.commons.applib.services.menu.model;
     exports org.apache.causeway.viewer.commons.applib;
+    exports org.apache.causeway.viewer.commons.applib.tabular.export;
     exports org.apache.causeway.viewer.commons.applib.mixins;
 
     requires static lombok;
diff --git a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/tabular/export/CollectionContentsExporter.java b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/tabular/export/CollectionContentsExporter.java
new file mode 100644
index 0000000000..386d6c9e5f
--- /dev/null
+++ b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/tabular/export/CollectionContentsExporter.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.causeway.viewer.commons.applib.tabular.export;
+
+import java.io.File;
+
+import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
+import org.apache.causeway.core.metamodel.interactions.managed.nonscalar.DataTableModel;
+
+/**
+ * SPI to provide file export to table views.
+ */
+public interface CollectionContentsExporter {
+
+    File createExportFile(DataTableModel dataTableModel);
+
+    CommonMimeType getMimeType();
+
+    /**
+     * @return label for the "View as" dropdown for "collection contents as"
+     * component factories
+     */
+    String getTitleLabel();
+
+    /**
+     * @return CSS class for the icon/image next to "View as" dropdown
+     * for "collection contents as" component factories
+     */
+    String getCssClass();
+
+    /**
+     * An ordinal, that governs the order of appearance in the UI dropdown.
+     * <ul>
+     * <li>{@literal 1000..1999} reserved for different table presentations</li>
+     * <li>{@literal 2000..2999} reserved for different table exports</li>
+     * </ul>
+     * <p>
+     * Lowest comes first.
+     */
+    int orderOfAppearanceInUiDropdown();
+
+    /**
+     * Whether activation of this table presentation view should result in a full page reload.
+     */
+    default boolean isPageReloadRequiredOnTableViewActivation() { return false; }
+}
diff --git a/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/tabular/export/ComponentModel.java b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/tabular/export/ComponentModel.java
new file mode 100644
index 0000000000..6f97516b66
--- /dev/null
+++ b/viewers/commons/applib/src/main/java/org/apache/causeway/viewer/commons/applib/tabular/export/ComponentModel.java
@@ -0,0 +1,58 @@
+/*
+ *  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.causeway.viewer.commons.applib.tabular.export;
+
+import java.io.Serializable;
+import java.util.function.Supplier;
+
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.commons.internal.functions._Functions.SerializableSupplier;
+
+import lombok.AllArgsConstructor;
+import lombok.NonNull;
+
+public abstract class ComponentModel<T> implements Supplier<T>, Serializable {
+    private static final long serialVersionUID = 1L;
+
+    public static <T extends Serializable> ComponentModel<T> eager(final T t) {
+        return new Eager<>(t);
+    }
+
+    public static <T> ComponentModel<T> lazy(final SerializableSupplier<T> supplier) {
+        return new Lazy<>(supplier);
+    }
+
+    // -- SPECIALIZATIONS
+
+    @AllArgsConstructor
+    private static class Eager<T extends Serializable> extends ComponentModel<T> {
+        private static final long serialVersionUID = 1L;
+        private @Nullable T t;
+        @Override @Nullable public T get() { return t; }
+    }
+
+    @AllArgsConstructor
+    private static class Lazy<T> extends ComponentModel<T> {
+        private static final long serialVersionUID = 1L;
+        private @NonNull SerializableSupplier<T> supplier;
+        @Override @Nullable public T get() { return supplier.get(); }
+    }
+
+}
diff --git a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/test/ComponentFactoryAbstractTest_init.java b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/test/ComponentFactoryAbstractTest_init.java
index d9bb9df9a3..8389f63566 100644
--- a/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/test/ComponentFactoryAbstractTest_init.java
+++ b/viewers/wicket/ui-test/src/test/java/org/apache/causeway/viewer/wicket/ui/test/ComponentFactoryAbstractTest_init.java
@@ -45,8 +45,6 @@ class ComponentFactoryAbstractTest_init {
     public void canInstantiateComponentFactoryWithNoComponentClass() {
         class ComponentFactoryWithNoComponentClass extends ComponentFactoryAbstract {
 
-            private static final long serialVersionUID = 1L;
-
             public ComponentFactoryWithNoComponentClass() {
                 super(null);
                 setMetaModelContext(commonContext);
@@ -72,8 +70,6 @@ class ComponentFactoryAbstractTest_init {
         class ComponentClass {}
         class ComponentFactoryWithComponentClass extends ComponentFactoryAbstract {
 
-            private static final long serialVersionUID = 1L;
-
             public ComponentFactoryWithComponentClass() {
                 super(null, ComponentClass.class);
                 setMetaModelContext(commonContext);
@@ -100,8 +96,6 @@ class ComponentFactoryAbstractTest_init {
 
         class ComponentFactoryWithIncorrectComponentClass extends ComponentFactoryAbstract {
 
-            private static final long serialVersionUID = 1L;
-
             public ComponentFactoryWithIncorrectComponentClass() {
                 super(null, ComponentFactoryWithIncorrectComponentClass.class);
                 setMetaModelContext(commonContext);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/ComponentFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/ComponentFactory.java
index 9ea6429dd1..0109d4ae8e 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/ComponentFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/ComponentFactory.java
@@ -18,13 +18,12 @@
  */
 package org.apache.causeway.viewer.wicket.ui;
 
-import java.io.Serializable;
-
 import org.apache.wicket.Component;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.request.resource.CssResourceReference;
 
 import org.apache.causeway.viewer.commons.model.components.UiComponentType;
+import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryKey;
 import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryRegistry;
 
 /**
@@ -43,7 +42,11 @@ import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryRegistr
  * discovered/managed by Spring, that is,
  * it needs a direct- or meta-annotation of type {@link Component}
  */
-public interface ComponentFactory extends Serializable {
+public interface ComponentFactory {
+
+    default ComponentFactoryKey key() {
+        return new ComponentFactoryKey(this);
+    }
 
     /**
      * The {@link UiComponentType} with which this component factory has been
@@ -117,6 +120,4 @@ public interface ComponentFactory extends Serializable {
 
     CssResourceReference getCssResourceReference();
 
-
-
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/ComponentFactoryAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/ComponentFactoryAbstract.java
index a1fe315c9a..73e14dc9f0 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/ComponentFactoryAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/ComponentFactoryAbstract.java
@@ -38,8 +38,6 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 public abstract class ComponentFactoryAbstract implements ComponentFactory {
 
-    private static final long serialVersionUID = 1L;
-
     @ToString.Exclude
     @Getter @Setter private transient MetaModelContext metaModelContext;
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/app/registry/ComponentFactoryKey.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/app/registry/ComponentFactoryKey.java
new file mode 100644
index 0000000000..c9790df5d3
--- /dev/null
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/app/registry/ComponentFactoryKey.java
@@ -0,0 +1,93 @@
+/*
+ *  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.causeway.viewer.wicket.ui.app.registry;
+
+import java.io.Serializable;
+import java.util.function.Supplier;
+
+import org.apache.wicket.model.IModel;
+
+import org.apache.causeway.applib.services.registry.ServiceRegistry;
+import org.apache.causeway.commons.internal.base._Casts;
+import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.viewer.commons.model.components.UiComponentType;
+import org.apache.causeway.viewer.wicket.ui.CollectionContentsAsFactory;
+import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
+
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.experimental.Accessors;
+
+/**
+ * Serializable key into the {@link ServiceRegistry} to lookup {@link ComponentFactory}s,
+ * in order for the latter not having to be {@link Serializable}.
+ */
+public final class ComponentFactoryKey implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @Getter @Accessors(fluent=true)
+    private final Class<? extends ComponentFactory> factoryClass;
+    @Getter @Accessors(fluent=true)
+    private final String id;
+    @Getter @Accessors(fluent=true)
+    private final String label;
+    @Getter @Accessors(fluent=true)
+    private final UiComponentType componentType;
+    @Getter @Accessors(fluent=true)
+    private int orderOfAppearanceInUiDropdown;
+    @Getter @Accessors(fluent=true)
+    private boolean isPageReloadRequiredOnTableViewActivation;
+    @Getter @Accessors(fluent=true)
+    private String cssClass;
+
+    // access only through resolve(...)
+    private transient ComponentFactory componentFactory;
+
+    public ComponentFactoryKey(final @NonNull ComponentFactory componentFactory) {
+        this.factoryClass = componentFactory.getClass();
+        this.componentFactory = componentFactory;
+        this.id = componentFactory.getName();
+        this.label = _Casts.castTo(CollectionContentsAsFactory.class, componentFactory)
+                .map(CollectionContentsAsFactory::getTitleLabel)
+                .map(IModel::getObject)
+                .orElseGet(()->componentFactory.getName());
+        this.componentType = componentFactory.getComponentType();
+        this.orderOfAppearanceInUiDropdown =
+                componentFactory instanceof CollectionContentsAsFactory
+                    ? ((CollectionContentsAsFactory) componentFactory).orderOfAppearanceInUiDropdown()
+                    : Integer.MAX_VALUE;
+        this.isPageReloadRequiredOnTableViewActivation =
+                componentFactory instanceof CollectionContentsAsFactory
+                    ? ((CollectionContentsAsFactory) componentFactory).isPageReloadRequiredOnTableViewActivation()
+                    : false;
+
+        this.cssClass = _Casts.castTo(CollectionContentsAsFactory.class, componentFactory)
+                .map(CollectionContentsAsFactory::getCssClass)
+                .map(IModel::getObject)
+                .orElseGet(()->
+                    _Strings.asLowerDashed.apply(componentFactory.getName()));
+    }
+
+    public ComponentFactory resolve(final @NonNull Supplier<ServiceRegistry> serviceRegistrySupplier) {
+        return componentFactory != null
+                ? componentFactory
+                : (this.componentFactory = serviceRegistrySupplier.get().lookupServiceElseFail(factoryClass));
+    }
+
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/about/AboutPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/about/AboutPanelFactory.java
index 7014b0a927..5b42879763 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/about/AboutPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/about/AboutPanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class AboutPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public AboutPanelFactory() {
         super(UiComponentType.ABOUT, AboutPanel.class);
     }
@@ -47,5 +45,4 @@ public class AboutPanelFactory extends ComponentFactoryAbstract {
         return new AboutPanel(id, new AboutModel(getMetaModelContext()));
     }
 
-
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actioninfo/ActionInfoPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actioninfo/ActionInfoPanelFactory.java
index 51e6315db5..8874f4ee1b 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actioninfo/ActionInfoPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actioninfo/ActionInfoPanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class ActionInfoPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public ActionInfoPanelFactory() {
         super(UiComponentType.ACTION_INFO, ActionInfoPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
index 4cb701ea9b..2e788fb943 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/ServiceActionsPanelFactory.java
@@ -36,8 +36,6 @@ import lombok.val;
  */
 public class ServiceActionsPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public ServiceActionsPanelFactory() {
         super(UiComponentType.SERVICE_ACTIONS, ServiceActionsPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
index f30701c8dd..0a906a94e3 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryMenuPanelFactory.java
@@ -35,8 +35,6 @@ import lombok.val;
  */
 public class TertiaryMenuPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public TertiaryMenuPanelFactory() {
         super(UiComponentType.SERVICE_ACTIONS, ServiceActionsPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actions/ActionParametersFormPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actions/ActionParametersFormPanelFactory.java
index c75eb4c851..29a2f2d095 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actions/ActionParametersFormPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actions/ActionParametersFormPanelFactory.java
@@ -32,8 +32,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
 public class ActionParametersFormPanelFactory
 extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public ActionParametersFormPanelFactory() {
         super(UiComponentType.PARAMETERS, ActionParametersFormPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actions/ActionParametersPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actions/ActionParametersPanelFactory.java
index a6c12d8892..40f03d1199 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actions/ActionParametersPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/actions/ActionParametersPanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class ActionParametersPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public ActionParametersPanelFactory() {
         super(UiComponentType.ACTION_PROMPT, ActionParametersPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanelFactory.java
index eaa6273c8f..895d79f213 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanelFactory.java
@@ -27,8 +27,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
 
 public class BookmarkedPagesPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public BookmarkedPagesPanelFactory() {
         super(UiComponentType.BOOKMARKED_PAGES, BookmarkedPagesPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorHelper.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorHelper.java
index 3376c6d5bb..0e99bb9c8a 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorHelper.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorHelper.java
@@ -36,6 +36,7 @@ import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModel;
 import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModelParented;
 import org.apache.causeway.viewer.wicket.model.util.ComponentHintKey;
 import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
+import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryKey;
 import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryRegistry;
 import org.apache.causeway.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory;
 import org.apache.causeway.viewer.wicket.ui.components.collectioncontents.multiple.CollectionContentsMultipleViewsPanelFactory;
@@ -52,7 +53,7 @@ public class CollectionPresentationSelectorHelper implements Serializable {
     private final EntityCollectionModel collectionModel;
 
     @Getter
-    private final Can<ComponentFactory> componentFactories;
+    private final Can<ComponentFactoryKey> componentFactories;
     private final ComponentHintKey componentHintKey;
 
     public CollectionPresentationSelectorHelper(
@@ -72,7 +73,7 @@ public class CollectionPresentationSelectorHelper implements Serializable {
                 : ComponentHintKey.noop();
     }
 
-    private Can<ComponentFactory> gatherComponentFactories(
+    private Can<ComponentFactoryKey> gatherComponentFactories(
             final ComponentFactoryRegistry componentFactoryRegistry) {
 
         return componentFactoryRegistry
@@ -82,6 +83,7 @@ public class CollectionPresentationSelectorHelper implements Serializable {
                 collectionModel)
         .filter(componentFactory ->
             componentFactory.getClass() != CollectionContentsMultipleViewsPanelFactory.class)
+        .map(ComponentFactory::key)
         .collect(Can.toCan());
     }
 
@@ -138,8 +140,8 @@ public class CollectionPresentationSelectorHelper implements Serializable {
             final String viewName = Facets.defaultViewName(collectionModel.getMetaModel())
                     .orElseThrow(); // null case guarded by if clause
 
-            for (ComponentFactory componentFactory : componentFactories) {
-                final String componentName = componentFactory.getName();
+            for (ComponentFactoryKey componentFactory : componentFactories) {
+                final String componentName = componentFactory.id();
                 if (componentName.equalsIgnoreCase(viewName)) {
                     return componentName;
                 }
@@ -172,8 +174,8 @@ public class CollectionPresentationSelectorHelper implements Serializable {
         .orElse(false);
     }
 
-    public ComponentFactory find(final String selected) {
-        ComponentFactory componentFactory = doFind(selected);
+    public ComponentFactoryKey find(final String selected) {
+        ComponentFactoryKey componentFactory = doFind(selected);
         if (componentFactory != null) {
             return componentFactory;
         }
@@ -190,9 +192,9 @@ public class CollectionPresentationSelectorHelper implements Serializable {
         return componentFactory;
     }
 
-    private ComponentFactory doFind(final String selected) {
-        for (ComponentFactory componentFactory : componentFactories) {
-            if(selected.equals(componentFactory.getName())) {
+    private ComponentFactoryKey doFind(final String selected) {
+        for (ComponentFactoryKey componentFactory : componentFactories) {
+            if(selected.equals(componentFactory.id())) {
                 return componentFactory;
             }
         }
@@ -201,8 +203,8 @@ public class CollectionPresentationSelectorHelper implements Serializable {
 
     public int lookup(final String view) {
         int i=0;
-        for (ComponentFactory componentFactory : componentFactories) {
-            if(view.equals(componentFactory.getName())) {
+        for (ComponentFactoryKey componentFactory : componentFactories) {
+            if(view.equals(componentFactory.id())) {
                 return i;
             }
             i++;
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.java
index f31c7da2fe..0048856350 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/CollectionPresentationSelectorPanel.java
@@ -20,6 +20,7 @@ package org.apache.causeway.viewer.wicket.ui.components.collection.selector;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -31,6 +32,7 @@ import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.DownloadLink;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
 
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.core.metamodel.interactions.managed.nonscalar.DataTableModel;
@@ -40,7 +42,7 @@ import org.apache.causeway.viewer.wicket.model.links.Menuable;
 import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModel;
 import org.apache.causeway.viewer.wicket.model.util.ComponentHintKey;
 import org.apache.causeway.viewer.wicket.ui.CollectionContentsAsFactory;
-import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
+import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryKey;
 import org.apache.causeway.viewer.wicket.ui.components.widgets.links.AjaxLinkNoPropagate;
 import org.apache.causeway.viewer.wicket.ui.panels.PanelAbstract;
 import org.apache.causeway.viewer.wicket.ui.util.Wkt;
@@ -75,7 +77,7 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
     private final CollectionPresentationSelectorHelper selectorHelper;
     private final ComponentHintKey componentHintKey;
 
-    private ComponentFactory selectedComponentFactory;
+    private ComponentFactoryKey selectedComponentFactory;
 
     public CollectionPresentationSelectorPanel(
             final String id,
@@ -104,7 +106,8 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
     }
 
     private void addDropdown() {
-        final Can<ComponentFactory> componentFactories = selectorHelper.getComponentFactories();
+        final Can<ComponentFactoryKey> componentFactories = selectorHelper.getComponentFactories();
+                //.map(key->key.resolve(this::getServiceRegistry));
         final String selected = selectorHelper.honourViewHintElseDefault(this);
 
         // selector
@@ -155,13 +158,13 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
             }
 
             val linkEntry = (LinkEntry) menuable;
-            final ComponentFactory componentFactory = linkEntry.getComponentFactory();
+            final ComponentFactoryKey componentFactory = linkEntry.getComponentFactoryKey();
 
             // add direct download link instead of a panel
-            if(componentFactory.getComponentType() == UiComponentType.COLLECTION_CONTENTS_EXPORT) {
+            if(componentFactory.componentType() == UiComponentType.COLLECTION_CONTENTS_EXPORT) {
 
                 final DownloadLink downloadLink = (DownloadLink)
-                        componentFactory.createComponent(ID_VIEW_LINK, getModel());
+                        componentFactory.resolve(this::getServiceRegistry).createComponent(ID_VIEW_LINK, getModel());
                 WktComponents.permanentlyHide(downloadLink, ID_VIEW_ITEM_CHECKMARK);
 
                 item.addOrReplace(downloadLink);
@@ -174,12 +177,12 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
             // on click: make the clicked item the new selected item
             val link = Wkt.linkAdd(item, ID_VIEW_LINK, target->{
                 final CollectionPresentationSelectorPanel linksSelectorPanel = CollectionPresentationSelectorPanel.this;
-                linksSelectorPanel.setViewHintAndBroadcast(componentFactory.getName(), target);
+                linksSelectorPanel.setViewHintAndBroadcast(componentFactory.id(), target);
 
                 linksSelectorPanel.selectedComponentFactory = componentFactory;
 
                 CollectionPresentationSelectorPanel.this.getModel().parentedHintingBookmark()
-                    .ifPresent(bookmark->componentHintKey.set(bookmark, componentFactory.getName()));
+                    .ifPresent(bookmark->componentHintKey.set(bookmark, componentFactory.id()));
 
                 /* [CAUSEWAY-3415] do a full page reload when required,
                  * to properly trigger all client side java-script, that decorates HTML (datatable.net, vega, ...) */
@@ -211,7 +214,7 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
      * @param filter
      * @see CollectionContentsAsFactory#orderOfAppearanceInUiDropdown()
      */
-    private List<Menuable> sorted(final Can<ComponentFactory> componentFactories) {
+    private List<Menuable> sorted(final Can<ComponentFactoryKey> componentFactories) {
         val presentations = sorted(componentFactories, _Util.filterTablePresentations());
         val exports = sorted(componentFactories, _Util.filterTableExports());
         val sortedWithSeparators = new ArrayList<Menuable>(
@@ -236,12 +239,12 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
     }
 
     private List<LinkEntry> sorted(
-            final Can<ComponentFactory> componentFactories,
-            final Predicate<? super ComponentFactory> filter) {
+            final Can<ComponentFactoryKey> componentFactories,
+            final Predicate<? super ComponentFactoryKey> filter) {
         final List<LinkEntry> sorted = componentFactories.stream()
             .filter(filter)
             .sorted(_Util.orderByOrderOfAppearanceInUiDropdown())
-            .map((final ComponentFactory factory)->LinkEntry.linkEntry(factory))
+            .map((final ComponentFactoryKey factory)->LinkEntry.linkEntry(factory))
             .collect(Collectors.toList());
         return sorted;
     }
@@ -270,21 +273,21 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
 
         // -- FACTORIES
 
-        public static LinkEntry linkEntry(final @NonNull ComponentFactory componentFactory) {
-            return new LinkEntry(componentFactory);
+        public static LinkEntry linkEntry(final @NonNull ComponentFactoryKey componentFactoryKey) {
+            return new LinkEntry(componentFactoryKey);
         }
 
         // -- CONSTRUCTION
 
-        final ComponentFactory componentFactory;
+        final ComponentFactoryKey componentFactoryKey;
 
         // -- PREDICATES
 
         boolean isSelectedIn(final CollectionPresentationSelectorPanel panel) {
-           return componentFactory == panel.selectedComponentFactory;
+           return Objects.equals(componentFactoryKey, panel.selectedComponentFactory);
         }
         boolean isPageReloadRequiredOnTableViewActivation() {
-            return _Util.isPageReloadRequiredOnTableViewActivation(componentFactory);
+            return componentFactoryKey.isPageReloadRequiredOnTableViewActivation();
         }
 
         /**
@@ -293,7 +296,8 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
          * that corresponds to the selected presentation.
          */
         void markAsSelected(final Label viewButtonIcon, final AjaxLinkNoPropagate link) {
-            final IModel<String> cssClass = _Util.cssClassFor(componentFactory, viewButtonIcon);
+            final IModel<String> cssClass = Model.of(componentFactoryKey.cssClass());
+            _Util.initViewIcon(componentFactoryKey, viewButtonIcon);
             Wkt.cssReplace(viewButtonIcon, "ViewLinkItem " + cssClass.getObject());
             link.setEnabled(false);
         }
@@ -301,10 +305,12 @@ extends PanelAbstract<DataTableModel, EntityCollectionModel> {
         // -- HELPER
 
         private static IModel<String> nameFor(final LinkEntry linkEntry) {
-            return _Util.nameFor(linkEntry.getComponentFactory());
+            return Model.of(linkEntry.getComponentFactoryKey().label());
         }
         private static IModel<String> cssClassFor(final LinkEntry linkEntry, final Label viewIcon) {
-            return _Util.cssClassFor(linkEntry.getComponentFactory(), viewIcon);
+            final IModel<String> cssClass = Model.of(linkEntry.componentFactoryKey.cssClass());
+            _Util.initViewIcon(linkEntry.componentFactoryKey, viewIcon);
+            return cssClass;
         }
 
         @Override
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/_Util.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/_Util.java
index d00b67b514..1925626724 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/_Util.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collection/selector/_Util.java
@@ -22,69 +22,40 @@ import java.util.Comparator;
 import java.util.function.Predicate;
 
 import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
 
-import org.apache.causeway.commons.internal.base._Casts;
-import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.viewer.commons.model.components.UiComponentType;
 import org.apache.causeway.viewer.wicket.ui.CollectionContentsAsFactory;
-import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
+import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryKey;
 
 import lombok.experimental.UtilityClass;
 
 @UtilityClass
 class _Util {
 
-    IModel<String> nameFor(final ComponentFactory componentFactory) {
-        return _Casts.castTo(CollectionContentsAsFactory.class, componentFactory)
-            .map(CollectionContentsAsFactory::getTitleLabel)
-            .orElseGet(()->Model.of(componentFactory.getName()));
+    void initViewIcon(final ComponentFactoryKey componentFactory, final Label viewIcon) {
+        if(CollectionContentsAsFactory.class.isAssignableFrom(componentFactory.factoryClass())) {
+            viewIcon.setDefaultModelObject("");
+            viewIcon.setEscapeModelStrings(true);
+        } else {
+            // Small hack: if there is no specific CSS class then we assume that background-image is used
+            // the span.ViewItemLink should have some content to show it
+            // FIX: find a way to do this with CSS (width and height don't seems to help)
+            viewIcon.setDefaultModelObject("&#160;&#160;&#160;&#160;&#160;");
+            viewIcon.setEscapeModelStrings(false);
+        }
     }
 
-    IModel<String> cssClassFor(final ComponentFactory componentFactory, final Label viewIcon) {
-        return _Casts.castTo(CollectionContentsAsFactory.class, componentFactory)
-            .map(CollectionContentsAsFactory::getCssClass)
-            .map(cssClass->{
-                viewIcon.setDefaultModelObject("");
-                viewIcon.setEscapeModelStrings(true);
-                return cssClass;
-            })
-            .orElseGet(()->{
-                // Small hack: if there is no specific CSS class then we assume that background-image is used
-                // the span.ViewItemLink should have some content to show it
-                // FIX: find a way to do this with CSS (width and height don't seems to help)
-                viewIcon.setDefaultModelObject("&#160;&#160;&#160;&#160;&#160;");
-                viewIcon.setEscapeModelStrings(false);
-                return Model.of(_Strings.asLowerDashed.apply(componentFactory.getName()));
-            });
+    Predicate<? super ComponentFactoryKey> filterTablePresentations() {
+        return f->f.componentType() == UiComponentType.COLLECTION_CONTENTS;
     }
-
-    int orderOfAppearanceInUiDropdownFor(final ComponentFactory componentFactory) {
-        return componentFactory instanceof CollectionContentsAsFactory
-                ? ((CollectionContentsAsFactory) componentFactory).orderOfAppearanceInUiDropdown()
-                : Integer.MAX_VALUE;
-    }
-
-    boolean isPageReloadRequiredOnTableViewActivation(final ComponentFactory componentFactory) {
-        return componentFactory instanceof CollectionContentsAsFactory
-                ? ((CollectionContentsAsFactory) componentFactory).isPageReloadRequiredOnTableViewActivation()
-                : false;
-    }
-
-    Predicate<? super ComponentFactory> filterTablePresentations() {
-        return f->f.getComponentType() == UiComponentType.COLLECTION_CONTENTS;
-    }
-    Predicate<? super ComponentFactory> filterTableExports() {
-        return f->f.getComponentType() == UiComponentType.COLLECTION_CONTENTS_EXPORT;
+    Predicate<? super ComponentFactoryKey> filterTableExports() {
+        return f->f.componentType() == UiComponentType.COLLECTION_CONTENTS_EXPORT;
     }
 
-    Comparator<? super ComponentFactory> orderByOrderOfAppearanceInUiDropdown() {
+    Comparator<? super ComponentFactoryKey> orderByOrderOfAppearanceInUiDropdown() {
         return (a, b)->Integer.compare(
-                _Util.orderOfAppearanceInUiDropdownFor(a),
-                _Util.orderOfAppearanceInUiDropdownFor(b));
+                a.orderOfAppearanceInUiDropdown(),
+                b.orderOfAppearanceInUiDropdown());
     }
 
-
-
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanelFactory.java
index 1486c00525..c9b8c892c5 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanelFactory.java
@@ -36,8 +36,6 @@ public class CollectionContentsAsAjaxTablePanelFactory
 extends ComponentFactoryAbstract
 implements CollectionContentsAsFactory {
 
-    private static final long serialVersionUID = 1L;
-
     public static final String NAME = "table";
 
     public CollectionContentsAsAjaxTablePanelFactory() {
diff --git a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CollectionContentsAsExcelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/export/CollectionContentsAsExportFactory.java
similarity index 52%
copy from extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CollectionContentsAsExcelFactory.java
copy to viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/export/CollectionContentsAsExportFactory.java
index 05daba541b..2a02916d5c 100644
--- a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/CollectionContentsAsExcelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/export/CollectionContentsAsExportFactory.java
@@ -16,21 +16,23 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.extensions.exceldownload.wkt.ui.viewer;
+package org.apache.causeway.viewer.wicket.ui.components.collectioncontents.export;
 
 import java.io.File;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.markup.html.link.DownloadLink;
 import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
 
+import org.apache.causeway.viewer.commons.applib.tabular.export.CollectionContentsExporter;
 import org.apache.causeway.viewer.commons.model.components.UiComponentType;
 import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModel;
 import org.apache.causeway.viewer.wicket.ui.CollectionContentsAsFactory;
 import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
 import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
+import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryKey;
+import org.apache.causeway.viewer.wicket.ui.components.download.FileDownloadLink;
 
 /**
  * {@link ComponentFactory} for {@link DownloadLink}.
@@ -38,16 +40,15 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  * @since 2.0 {@index}
  */
 @org.springframework.stereotype.Component
-public class CollectionContentsAsExcelFactory
+public class CollectionContentsAsExportFactory
 extends ComponentFactoryAbstract
 implements CollectionContentsAsFactory {
 
-    private static final long serialVersionUID = 1L;
+    private final CollectionContentsExporter collectionContentsExporter;
 
-    private static final String NAME = "excel";
-
-    public CollectionContentsAsExcelFactory() {
-        super(UiComponentType.COLLECTION_CONTENTS_EXPORT, NAME, DownloadLink.class);
+    public CollectionContentsAsExportFactory(final CollectionContentsExporter collectionContentsExporter) {
+        super(UiComponentType.COLLECTION_CONTENTS_EXPORT, collectionContentsExporter.getClass().getName(), DownloadLink.class);
+        this.collectionContentsExporter = collectionContentsExporter;
     }
 
     @Override
@@ -60,35 +61,50 @@ implements CollectionContentsAsFactory {
 
     @Override
     public Component createComponent(final String id, final IModel<?> model) {
-        final EntityCollectionModel collectionModel = (EntityCollectionModel) model;
-        return createDownloadLink(id, collectionModel);
+        var collectionModel = (EntityCollectionModel) model;
+        var mimeType = collectionContentsExporter.getMimeType();
+        var ext = mimeType.getProposedFileExtensions().getFirstElseFail();
+        var fileName = collectionModel.getName().replaceAll(" ", "") + "." + ext;
+
+        var link = new FileDownloadLink(id,
+                collectionContentsExporter.getMimeType(),
+                fileName, new FileModel(this, collectionModel));
+        return link;
     }
 
     @Override
     public IModel<String> getTitleLabel() {
-        return Model.of("Excel Download");
+        return Model.of(collectionContentsExporter.getTitleLabel());
     }
 
     @Override
     public IModel<String> getCssClass() {
-        return Model.of("fa-solid fa-file-excel");
+        return Model.of(collectionContentsExporter.getCssClass());
     }
 
     @Override
     public int orderOfAppearanceInUiDropdown() {
-        return 2500;
+        return collectionContentsExporter.orderOfAppearanceInUiDropdown();
     }
 
-    // -- HELPER
+    // --
 
-    private DownloadLink createDownloadLink(final String id, final EntityCollectionModel model) {
-        final LoadableDetachableModel<File> fileModel = ExcelFileModel.of(model);
-        final String xlsxFileName = xlsxFileNameFor(model);
-        final DownloadLink link = new ExcelFileDownloadLink(id, fileModel, xlsxFileName);
-        return link;
-    }
+    static class FileModel implements IModel<File> {
+        private static final long serialVersionUID = 1L;
+        private ComponentFactoryKey key;
+        private EntityCollectionModel model;
+
+        FileModel(final CollectionContentsAsExportFactory x, final EntityCollectionModel model) {
+            this.key = x.key();
+            this.model = model;
+        }
+
+        @Override
+        public File getObject() {
+            return ((CollectionContentsAsExportFactory) key.resolve(model::getServiceRegistry))
+            .collectionContentsExporter.createExportFile(model.getDataTableModel());
+        }
 
-    private static String xlsxFileNameFor(final EntityCollectionModel model) {
-        return model.getName().replaceAll(" ", "") + ".xlsx";
     }
+
 }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/icons/CollectionContentsAsIconsPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/icons/CollectionContentsAsIconsPanelFactory.java
index d0e04b75a2..3ffe92e431 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/icons/CollectionContentsAsIconsPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/icons/CollectionContentsAsIconsPanelFactory.java
@@ -35,8 +35,6 @@ import lombok.val;
 // TODO mgrigorov: Is this still needed ? It seems it is not used.
 public class CollectionContentsAsIconsPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     private static final String NAME = "icons";
 
     public CollectionContentsAsIconsPanelFactory() {
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java
index bed2b14693..0fa55e78d1 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanel.java
@@ -32,7 +32,7 @@ import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModelAbstr
 import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModelHidden;
 import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModelParented;
 import org.apache.causeway.viewer.wicket.model.util.ComponentHintKey;
-import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
+import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryKey;
 import org.apache.causeway.viewer.wicket.ui.components.collection.count.CollectionCountProvider;
 import org.apache.causeway.viewer.wicket.ui.components.collection.selector.CollectionPresentationSelectorHelper;
 import org.apache.causeway.viewer.wicket.ui.components.collection.selector.CollectionPresentationSelectorPanel;
@@ -93,13 +93,13 @@ implements CollectionCountProvider {
         final EntityCollectionModel hiddenCollModel = EntityCollectionModelHidden
                 .forCollectionModel((EntityCollectionModelAbstract) visibleCollModel);
 
-        final Can<ComponentFactory> componentFactories = selectorHelper.getComponentFactories();
+        final Can<ComponentFactoryKey> componentFactories = selectorHelper.getComponentFactories();
 
         final CollectionPresentationSelectorPanel selectorDropdownPanelIfAny =
                 CollectionPresentationSelectorProvider.getCollectionSelectorProvider(this);
         final String selectedCompFactoryName = selectorDropdownPanelIfAny != null
             ? selectorHelper.honourViewHintElseDefault(selectorDropdownPanelIfAny)
-            : componentFactories.getFirstElseFail().getName();
+            : componentFactories.getFirstElseFail().id();
 
 
         // create all, hide the one not selected
@@ -107,11 +107,11 @@ implements CollectionCountProvider {
         int selectedIdx = 0;
         underlyingViews = new Component[MAX_NUM_UNDERLYING_VIEWS];
 
-        for (ComponentFactory componentFactory : componentFactories) {
+        for (ComponentFactoryKey componentFactory : componentFactories) {
             final String underlyingId = underlyingIdPrefix + "-" + i;
 
-            final boolean isSelected = selectedCompFactoryName.equals(componentFactory.getName());
-            final Component underlyingView = componentFactory
+            final boolean isSelected = selectedCompFactoryName.equals(componentFactory.id());
+            final Component underlyingView = componentFactory.resolve(this::getServiceRegistry)
                     .createComponent(underlyingId, isSelected ? visibleCollModel : hiddenCollModel);
             if(isSelected) {
                 selectedIdx = i;
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanelFactory.java
index c8a56e2e1e..191f64d54e 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/multiple/CollectionContentsMultipleViewsPanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class CollectionContentsMultipleViewsPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public CollectionContentsMultipleViewsPanelFactory() {
         super(UiComponentType.COLLECTION_CONTENTS, CollectionContentsMultipleViewsPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/summary/CollectionContentsAsSummaryFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/summary/CollectionContentsAsSummaryFactory.java
index ad6f9329bb..a12c58344b 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/summary/CollectionContentsAsSummaryFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/summary/CollectionContentsAsSummaryFactory.java
@@ -47,8 +47,6 @@ public class CollectionContentsAsSummaryFactory
 extends ComponentFactoryAbstract
 implements CollectionContentsAsFactory {
 
-    private static final long serialVersionUID = 1L;
-
     private static final String NAME = "summary";
 
     static final Predicate<ObjectAssociation> OF_TYPE_BIGDECIMAL = (final ObjectAssociation objectAssoc) -> {
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/unresolved/CollectionContentsHiddenPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/unresolved/CollectionContentsHiddenPanelFactory.java
index abf447d11f..e64f9f0fc8 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/unresolved/CollectionContentsHiddenPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/unresolved/CollectionContentsHiddenPanelFactory.java
@@ -36,8 +36,6 @@ public class CollectionContentsHiddenPanelFactory
 extends ComponentFactoryAbstract
 implements CollectionContentsAsFactory {
 
-    private static final long serialVersionUID = 1L;
-
     public static final String NAME = "hidden";
 
     public CollectionContentsHiddenPanelFactory() {
@@ -55,8 +53,7 @@ implements CollectionContentsAsFactory {
 
     @Override
     public Component createComponent(final String id, final IModel<?> model) {
-        final EntityCollectionModel collectionModel = (EntityCollectionModel) model;
-        return new CollectionContentsHiddenPanel(id, collectionModel);
+        return new CollectionContentsHiddenPanel(id, (EntityCollectionModel) model);
     }
 
     @Override
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/download/FileDownloadLink.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/download/FileDownloadLink.java
new file mode 100644
index 0000000000..f2115d5432
--- /dev/null
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/download/FileDownloadLink.java
@@ -0,0 +1,60 @@
+/*
+ *  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.causeway.viewer.wicket.ui.components.download;
+
+import java.io.File;
+
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.link.DownloadLink;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.util.encoding.UrlEncoder;
+
+import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
+import org.apache.causeway.viewer.wicket.ui.util.Wkt;
+
+public class FileDownloadLink extends DownloadLink {
+
+    private static final long serialVersionUID = 1L;
+
+    private final CommonMimeType mime;
+    private final String fileName;
+
+    public FileDownloadLink(
+            final String id, final CommonMimeType mime, final String fileName,
+            final IModel<File> model) {
+        super(id, model, fileName);
+        this.mime = mime;
+        this.fileName = fileName;
+    }
+
+    @Override
+    public void onClick() {
+        // lazily creates a temporary file on the host, to be consumed for download and then deleted by this handler
+        Wkt.fileDownloadClickHandler(getModel(), mime, fileName);
+    }
+
+    @Override
+    protected void onComponentTag(final ComponentTag tag) {
+        super.onComponentTag(tag);
+        var encodedFileName =
+                UrlEncoder.QUERY_INSTANCE.encode(fileName, getRequest().getCharset());
+        tag.put("download", encodedFileName);
+    }
+
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/empty/EmptyCollectionPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/empty/EmptyCollectionPanelFactory.java
index 6967b1e194..2cd209c5f1 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/empty/EmptyCollectionPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/empty/EmptyCollectionPanelFactory.java
@@ -28,8 +28,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
 
 public class EmptyCollectionPanelFactory extends ComponentFactoryAbstract implements ComponentFactory {
 
-    private static final long serialVersionUID = 1L;
-
     public EmptyCollectionPanelFactory() {
         super(UiComponentType.EMPTY_COLLECTION, EmptyCollectionPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/EntityComponentFactoryAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/EntityComponentFactoryAbstract.java
index 7f8bf1971b..346038b13c 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/EntityComponentFactoryAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/EntityComponentFactoryAbstract.java
@@ -34,8 +34,6 @@ import lombok.val;
  */
 public abstract class EntityComponentFactoryAbstract extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public EntityComponentFactoryAbstract(
             final UiComponentType uiComponentType,
             final Class<?> componentClass) {
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/collection/EntityCollectionPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/collection/EntityCollectionPanelFactory.java
index 77ca208835..54363ad9b6 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/collection/EntityCollectionPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/collection/EntityCollectionPanelFactory.java
@@ -39,8 +39,6 @@ import lombok.val;
  */
 public class EntityCollectionPanelFactory extends EntityComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     /**
      * Helper class, used to call this factory.
      */
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/header/EntityHeaderPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/header/EntityHeaderPanelFactory.java
index 0cb0df81a4..f91fc25494 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/header/EntityHeaderPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/header/EntityHeaderPanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.components.entity.EntityComponentFac
  */
 public class EntityHeaderPanelFactory extends EntityComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public EntityHeaderPanelFactory() {
         super(UiComponentType.ENTITY_SUMMARY, EntityHeaderPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanelFactory.java
index fae8a8caf6..fbc9bcb91e 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanelFactory.java
@@ -43,8 +43,6 @@ import lombok.val;
  */
 public class EntityIconAndTitlePanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public EntityIconAndTitlePanelFactory(
             final UiComponentType uiComponentType,
             final Class<?> componentClass) {
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanelFactory.java
index 312f54ca40..3e5e23e9da 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanelFactory.java
@@ -26,12 +26,11 @@ import org.apache.causeway.viewer.wicket.model.models.UiObjectWkt;
 import org.apache.causeway.viewer.wicket.ui.components.entity.EntityComponentFactoryAbstract;
 
 /**
- * {@link org.apache.causeway.viewer.wicket.ui.ComponentFactory} for {@link org.apache.causeway.viewer.wicket.ui.components.entity.icontitle.EntityIconAndTitlePanel}.
+ * {@link org.apache.causeway.viewer.wicket.ui.ComponentFactory} for
+ * {@link org.apache.causeway.viewer.wicket.ui.components.entity.icontitle.EntityIconAndTitlePanel}.
  */
 public class EntityIconTitleAndCopyLinkPanelFactory extends EntityComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public EntityIconTitleAndCopyLinkPanelFactory() {
         super(UiComponentType.ENTITY_ICON_TITLE_AND_COPYLINK, EntityIconTitleAndCopyLinkPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/footer/FooterPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/footer/FooterPanelFactory.java
index baadd225a4..f36e9bc4ad 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/footer/FooterPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/footer/FooterPanelFactory.java
@@ -29,8 +29,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class FooterPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public FooterPanelFactory() {
         super(UiComponentType.FOOTER, FooterPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/header/HeaderPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/header/HeaderPanelFactory.java
index 1e13a972fe..22e3f46f00 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/header/HeaderPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/header/HeaderPanelFactory.java
@@ -29,8 +29,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class HeaderPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public HeaderPanelFactory() {
         super(UiComponentType.HEADER, HeaderPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/BSGridPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/BSGridPanelFactory.java
index ee4debf417..902723e6a9 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/BSGridPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/BSGridPanelFactory.java
@@ -34,8 +34,6 @@ import lombok.val;
  */
 public class BSGridPanelFactory extends EntityComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public BSGridPanelFactory() {
         super(UiComponentType.ENTITY, BSGridPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/property/PropertyEditFormPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/property/PropertyEditFormPanelFactory.java
index 403c255f2e..7b1737db20 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/property/PropertyEditFormPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/property/PropertyEditFormPanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class PropertyEditFormPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public PropertyEditFormPanelFactory() {
         super(UiComponentType.PROPERTY_EDIT_FORM, PropertyEditFormPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/property/PropertyEditPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/property/PropertyEditPanelFactory.java
index 4bc1f1c491..1733388226 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/property/PropertyEditPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/property/PropertyEditPanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class PropertyEditPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public PropertyEditPanelFactory() {
         super(UiComponentType.PROPERTY_EDIT_PROMPT, PropertyEditPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ComponentFactoryScalarAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ComponentFactoryScalarAbstract.java
index 29cd988e64..0156e1d31a 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ComponentFactoryScalarAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ComponentFactoryScalarAbstract.java
@@ -28,8 +28,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
 public abstract class ComponentFactoryScalarAbstract
 extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     protected ComponentFactoryScalarAbstract(
             final Class<?> componentClass) {
         super(UiComponentType.SCALAR_NAME_AND_VALUE, componentClass);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ComponentFactoryScalarTypeConstrainedAbstract.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ComponentFactoryScalarTypeConstrainedAbstract.java
index 4fff995ec0..5a7d68b273 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ComponentFactoryScalarTypeConstrainedAbstract.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/ComponentFactoryScalarTypeConstrainedAbstract.java
@@ -26,8 +26,6 @@ import lombok.Getter;
 public abstract class ComponentFactoryScalarTypeConstrainedAbstract
 extends ComponentFactoryScalarAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     /**
      * Provides discrimination based on {@link ScalarModel#isScalarTypeAnyOf(Can)}.
      * <p>
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/blobclob/CausewayBlobPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/blobclob/CausewayBlobPanelFactory.java
index 712d290b90..5b58a2352f 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/blobclob/CausewayBlobPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/blobclob/CausewayBlobPanelFactory.java
@@ -29,8 +29,6 @@ import org.apache.causeway.viewer.wicket.ui.components.scalars.ComponentFactoryS
  */
 public class CausewayBlobPanelFactory extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public CausewayBlobPanelFactory() {
         super(CausewayBlobPanel.class, org.apache.causeway.applib.value.Blob.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/blobclob/CausewayClobPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/blobclob/CausewayClobPanelFactory.java
index 53f74f85a2..cc6a852109 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/blobclob/CausewayClobPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/blobclob/CausewayClobPanelFactory.java
@@ -29,8 +29,6 @@ import org.apache.causeway.viewer.wicket.ui.components.scalars.ComponentFactoryS
  */
 public class CausewayClobPanelFactory extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public CausewayClobPanelFactory() {
         super(CausewayClobPanel.class, org.apache.causeway.applib.value.Clob.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/bool/BooleanPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/bool/BooleanPanelFactory.java
index 1fd17169c7..d5f48e6706 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/bool/BooleanPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/bool/BooleanPanelFactory.java
@@ -30,8 +30,6 @@ import org.apache.causeway.viewer.wicket.ui.components.scalars.ComponentFactoryS
  */
 public class BooleanPanelFactory extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public BooleanPanelFactory() {
         super(BooleanPanel.class, Can.of(boolean.class, Boolean.class));
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/choices/ChoicesSelect2PanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/choices/ChoicesSelect2PanelFactory.java
index 7ac90d9750..bb58129d2b 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/choices/ChoicesSelect2PanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/choices/ChoicesSelect2PanelFactory.java
@@ -29,8 +29,6 @@ import lombok.val;
 public class ChoicesSelect2PanelFactory
 extends ComponentFactoryScalarAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     private static enum ComponentSort {
         TITLE_BADGE,
         VALUE_CHOICES,
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/image/JavaAwtImagePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/image/JavaAwtImagePanelFactory.java
index 7e2b328e99..8939ab3085 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/image/JavaAwtImagePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/image/JavaAwtImagePanelFactory.java
@@ -30,8 +30,6 @@ import lombok.val;
 public class JavaAwtImagePanelFactory
 extends ComponentFactoryScalarAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public JavaAwtImagePanelFactory() {
         super(JavaAwtImagePanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/markup/MarkupPanelFactories.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/markup/MarkupPanelFactories.java
index 9bd04f2a2c..a2864c7ea0 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/markup/MarkupPanelFactories.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/markup/MarkupPanelFactories.java
@@ -53,9 +53,6 @@ public class MarkupPanelFactories {
 
     public static abstract class ParentedAbstract<T extends Serializable>
     extends ComponentFactoryScalarTypeConstrainedAbstract {
-
-        private static final long serialVersionUID = 1L;
-
         private final Class<T> valueType;
 
         protected ParentedAbstract(final Class<T> valueType) {
@@ -65,7 +62,7 @@ public class MarkupPanelFactories {
 
         @Override
         protected Component createComponent(final String id, final ScalarModel scalarModel) {
-            return new ScalarMarkupPanel<T>(id, scalarModel, valueType, this::newMarkupComponent);
+            return new ScalarMarkupPanel<T>(id, scalarModel, valueType, this.key());
         }
 
         protected abstract MarkupComponent newMarkupComponent(String id, ScalarModel model);
@@ -74,8 +71,8 @@ public class MarkupPanelFactories {
 
     // -- STANDALONE (ABSTRACT)
 
-    public static abstract class StandaloneAbstract<T> extends ComponentFactoryAbstract {
-        private static final long serialVersionUID = 1L;
+    public static abstract class StandaloneAbstract<T>
+    extends ComponentFactoryAbstract {
 
         private final Class<T> valueType;
 
@@ -111,7 +108,6 @@ public class MarkupPanelFactories {
     // -- CONCRETE COMPONENT FACTORY - PARENTED
 
     static class Parented extends ParentedAbstract<Markup> {
-        private static final long serialVersionUID = 1L;
 
         public Parented() {
             super(Markup.class);
@@ -129,7 +125,6 @@ public class MarkupPanelFactories {
     // -- CONCRETE COMPONENT FACTORY - STANDALONE
 
     static class Standalone extends StandaloneAbstract<Markup> {
-        private static final long serialVersionUID = 1L;
 
         public Standalone() {
             super(Markup.class);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/markup/ScalarMarkupPanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/markup/ScalarMarkupPanel.java
index da2043fd55..51a5ebcda9 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/markup/ScalarMarkupPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/markup/ScalarMarkupPanel.java
@@ -23,6 +23,7 @@ import java.util.EnumSet;
 import org.apache.wicket.Component;
 
 import org.apache.causeway.viewer.wicket.model.models.ScalarModel;
+import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryKey;
 import org.apache.causeway.viewer.wicket.ui.components.scalars.ScalarPanelTextFieldWithValueSemantics;
 
 /**
@@ -32,15 +33,15 @@ public class ScalarMarkupPanel<T>
 extends ScalarPanelTextFieldWithValueSemantics<T> {
 
     private static final long serialVersionUID = 1L;
-    private final MarkupComponentFactory<ScalarModel> markupComponentFactory; // serializable!
+    private final ComponentFactoryKey markupComponentFactoryKey; // serializable!
 
     public ScalarMarkupPanel(
             final String id,
             final ScalarModel scalarModel,
             final Class<T> valueType,
-            final MarkupComponentFactory<ScalarModel> markupComponentFactory) {
+            final ComponentFactoryKey markupComponentFactoryKey) {
         super(id, scalarModel, valueType);
-        this.markupComponentFactory = markupComponentFactory;
+        this.markupComponentFactoryKey = markupComponentFactoryKey;
     }
 
     @Override
@@ -55,6 +56,10 @@ extends ScalarPanelTextFieldWithValueSemantics<T> {
     }
 
     protected final MarkupComponent createMarkupComponent(final String id) {
+        @SuppressWarnings("unchecked")
+        MarkupComponentFactory<ScalarModel> markupComponentFactory =
+                (MarkupComponentFactory<ScalarModel>) markupComponentFactoryKey.resolve(this::getServiceRegistry);
+
         return markupComponentFactory.newMarkupComponent(id, scalarModel());
     }
 
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/passwd/CausewayPasswordPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/passwd/CausewayPasswordPanelFactory.java
index 6af8f5292e..8739061cb9 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/passwd/CausewayPasswordPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/passwd/CausewayPasswordPanelFactory.java
@@ -29,8 +29,6 @@ import org.apache.causeway.viewer.wicket.ui.components.scalars.ComponentFactoryS
  */
 public class CausewayPasswordPanelFactory extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public CausewayPasswordPanelFactory() {
         super(CausewayPasswordPanel.class, org.apache.causeway.applib.value.Password.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/string/StringPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/string/StringPanelFactory.java
index ad61e7429f..f336492248 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/string/StringPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/string/StringPanelFactory.java
@@ -26,8 +26,6 @@ import org.apache.causeway.viewer.wicket.ui.components.scalars.ComponentFactoryS
 
 public class StringPanelFactory extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public StringPanelFactory() {
         super(StringPanel.class, String.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/value/fallback/ValueFallbackPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/value/fallback/ValueFallbackPanelFactory.java
index e7eb7f89e6..85bbebfdfc 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/value/fallback/ValueFallbackPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/scalars/value/fallback/ValueFallbackPanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.components.scalars.ComponentFactoryS
 public class ValueFallbackPanelFactory
 extends ComponentFactoryScalarAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public ValueFallbackPanelFactory() {
         // not asking the super-type to validate types, so no value types need be provided.
         super(ValueFallbackPanel.class);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/standalonecollection/StandaloneCollectionPanel.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/standalonecollection/StandaloneCollectionPanel.java
index 0ead7c8e97..c13c542ab3 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/standalonecollection/StandaloneCollectionPanel.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/standalonecollection/StandaloneCollectionPanel.java
@@ -23,14 +23,13 @@ import java.util.Optional;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.wicket.model.Model;
 
 import org.apache.causeway.applib.annotation.TableDecorator;
 import org.apache.causeway.core.metamodel.interactions.managed.nonscalar.DataTableModel;
 import org.apache.causeway.viewer.commons.model.components.UiComponentType;
 import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModel;
 import org.apache.causeway.viewer.wicket.model.models.EntityCollectionModelStandalone;
-import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
+import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryKey;
 import org.apache.causeway.viewer.wicket.ui.components.collection.count.CollectionCountProvider;
 import org.apache.causeway.viewer.wicket.ui.components.collection.selector.CollectionPresentationSelectorHelper;
 import org.apache.causeway.viewer.wicket.ui.components.collection.selector.CollectionPresentationSelectorPanel;
@@ -88,12 +87,8 @@ implements CollectionCountProvider, CollectionPresentationSelectorProvider {
             final CollectionPresentationSelectorPanel selectorDropdownPanel =
                     new CollectionPresentationSelectorPanel(ID_SELECTOR_DROPDOWN, collectionModel);
 
-            final Model<ComponentFactory> componentFactoryModel = new Model<>();
-
             final String selected = selectorHelper.honourViewHintElseDefault(selectorDropdownPanel);
-            ComponentFactory selectedComponentFactory = selectorHelper.find(selected);
-
-            componentFactoryModel.setObject(selectedComponentFactory);
+            ComponentFactoryKey selectedComponentFactory = selectorHelper.find(selected); //TODO[CAUSEWAY-3630] unused
 
             outerDiv.setOutputMarkupId(true);
             outerDiv.addOrReplace(selectorDropdownPanel);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/standalonecollection/StandaloneCollectionPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/standalonecollection/StandaloneCollectionPanelFactory.java
index 7caa319edc..e205fcfbea 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/standalonecollection/StandaloneCollectionPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/standalonecollection/StandaloneCollectionPanelFactory.java
@@ -33,8 +33,6 @@ import lombok.val;
  */
 public class StandaloneCollectionPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public StandaloneCollectionPanelFactory() {
         super(UiComponentType.STANDALONE_COLLECTION, StandaloneCollectionPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/tree/TreePanelFactories.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/tree/TreePanelFactories.java
index 50285ae7ca..bc95a9ea97 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/tree/TreePanelFactories.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/tree/TreePanelFactories.java
@@ -38,7 +38,6 @@ public class TreePanelFactories {
     // -- PARENTED
 
     static class Parented extends ComponentFactoryScalarAbstract {
-        private static final long serialVersionUID = 1L;
 
         public Parented() {
             super(ParentedTreePanel.class);
@@ -61,7 +60,6 @@ public class TreePanelFactories {
     // -- STANDALONE
 
     static class Standalone extends ComponentFactoryAbstract {
-        private static final long serialVersionUID = 1L;
 
         public Standalone() {
             super(UiComponentType.VALUE, StandaloneTreePanel.class);
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/unknown/UnknownModelPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/unknown/UnknownModelPanelFactory.java
index d78bc3058a..962cd6b458 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/unknown/UnknownModelPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/unknown/UnknownModelPanelFactory.java
@@ -28,8 +28,6 @@ import org.apache.causeway.viewer.wicket.ui.panels.PanelUtil;
 
 public class UnknownModelPanelFactory implements ComponentFactory {
 
-    private static final long serialVersionUID = 1L;
-
     @Override
     public ApplicationAdvice appliesTo(final UiComponentType uiComponentType, final IModel<?> model) {
         return ApplicationAdvice.APPLIES;
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/value/StandaloneValuePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/value/StandaloneValuePanelFactory.java
index bbd8ffb248..fe7ec502ca 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/value/StandaloneValuePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/value/StandaloneValuePanelFactory.java
@@ -32,8 +32,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
 public class StandaloneValuePanelFactory
 extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public StandaloneValuePanelFactory() {
         super(UiComponentType.VALUE, StandaloneValuePanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/voidreturn/VoidReturnPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/voidreturn/VoidReturnPanelFactory.java
index a92b54637a..21772db1b1 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/voidreturn/VoidReturnPanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/voidreturn/VoidReturnPanelFactory.java
@@ -30,8 +30,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class VoidReturnPanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public VoidReturnPanelFactory() {
         super(UiComponentType.VOID_RETURN, VoidReturnPanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/welcome/WelcomePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/welcome/WelcomePanelFactory.java
index 97c17738ab..71d8186710 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/welcome/WelcomePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/welcome/WelcomePanelFactory.java
@@ -31,8 +31,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
  */
 public class WelcomePanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public WelcomePanelFactory() {
         super(UiComponentType.WELCOME, WelcomePanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/entitysimplelink/EntityLinkSimplePanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/entitysimplelink/EntityLinkSimplePanelFactory.java
index 79c147611c..33ba0f1dc7 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/entitysimplelink/EntityLinkSimplePanelFactory.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/entitysimplelink/EntityLinkSimplePanelFactory.java
@@ -29,8 +29,6 @@ import org.apache.causeway.viewer.wicket.ui.ComponentFactoryAbstract;
 
 public class EntityLinkSimplePanelFactory extends ComponentFactoryAbstract {
 
-    private static final long serialVersionUID = 1L;
-
     public EntityLinkSimplePanelFactory() {
         super(UiComponentType.ENTITY_LINK, EntityLinkSimplePanel.class);
     }
diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/util/Wkt.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/util/Wkt.java
index b88d3ae00a..703021538f 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/util/Wkt.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/util/Wkt.java
@@ -20,6 +20,7 @@ package org.apache.causeway.viewer.wicket.ui.util;
 
 import static de.agilecoders.wicket.jquery.JQuery.$;
 
+import java.io.File;
 import java.util.List;
 import java.util.Optional;
 import java.util.OptionalInt;
@@ -69,12 +70,18 @@ import org.apache.wicket.markup.repeater.OddEvenItem;
 import org.apache.wicket.markup.repeater.RepeatingView;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
+import org.apache.wicket.request.IRequestCycle;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.ContentDisposition;
 import org.apache.wicket.request.resource.CssResourceReference;
 import org.apache.wicket.request.resource.IResource;
 import org.apache.wicket.request.resource.ResourceReference;
 import org.apache.wicket.resource.JQueryPluginResourceReference;
 import org.apache.wicket.util.convert.IConverter;
+import org.apache.wicket.util.file.Files;
+import org.apache.wicket.util.resource.FileResourceStream;
 import org.apache.wicket.validation.IValidationError;
 import org.apache.wicket.validation.ValidationError;
 import org.apache.wicket.validation.validator.StringValidator;
@@ -84,10 +91,12 @@ import org.danekja.java.util.function.serializable.SerializableConsumer;
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.applib.Identifier;
+import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
 import org.apache.causeway.commons.internal.base._Casts;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.commons.internal.debug._Probe;
 import org.apache.causeway.commons.internal.debug._Probe.EntryPoint;
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.commons.internal.functions._Functions.SerializableFunction;
 import org.apache.causeway.commons.internal.functions._Functions.SerializableSupplier;
 import org.apache.causeway.core.config.CausewayConfiguration.Viewer.Wicket;
@@ -574,6 +583,43 @@ public class Wkt {
         return new ResourceLinkVolatile(id, resourceModel);
     }
 
+    // -- FILE DOWNLOAD
+
+    /**
+     * Schedules a file download within the context of the current {@link RequestCycle}.
+     * @param model - provides a (temporary) file located at the host, which is deleted after consumption
+     */
+    public void fileDownloadClickHandler(final IModel<File> model, final CommonMimeType mime, final String fileName) {
+        final File file = model.getObject();
+        if (file == null) {
+            throw _Exceptions.illegalState("Failed to retrieve a File object from model %s", model.getClass().getName());
+        }
+        RequestCycle.get().scheduleRequestHandlerAfterCurrent(
+                Wkt.fileResourceStreamRequestHandler(file, mime)
+                .setFileName(fileName)
+                .setContentDisposition(ContentDisposition.ATTACHMENT));
+    }
+
+    private ResourceStreamRequestHandler fileResourceStreamRequestHandler(final File file, final CommonMimeType mime) {
+        return new ResourceStreamRequestHandler(Wkt.fileResourceStream(file, mime)){
+            @Override public void respond(final IRequestCycle requestCycle) {
+                super.respond(requestCycle);
+                Files.remove(file);
+            }
+        };
+    }
+
+    private FileResourceStream fileResourceStream(final File file, final CommonMimeType mime) {
+        return new FileResourceStream(
+                new org.apache.wicket.util.file.File(file)) {
+            private static final long serialVersionUID = 1L;
+            @Override public String getContentType() {
+                return mime.getBaseType();
+                        //legacy "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml";
+            }
+        };
+    }
+
     // -- FILE UPLOAD
 
     public FileUploadField fileUploadField(
@@ -1095,6 +1141,4 @@ public class Wkt {
         }
     }
 
-
-
 }
diff --git a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/util/WktModel.java
similarity index 54%
rename from extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java
rename to viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/util/WktModel.java
index 506c259714..d12c8496b1 100644
--- a/extensions/vw/exceldownload/wicket-ui/src/main/java/org/apache/causeway/extensions/exceldownload/wkt/ui/viewer/_TimeConversion.java
+++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/util/WktModel.java
@@ -16,34 +16,27 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.causeway.extensions.exceldownload.wkt.ui.viewer;
+package org.apache.causeway.viewer.wicket.ui.util;
 
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.time.ZoneId;
-import java.util.Date;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LambdaModel;
 
-final class _TimeConversion {
+import org.apache.causeway.viewer.commons.applib.tabular.export.ComponentModel;
 
-    public static Date toDate(LocalDate value) {
-        return toDate(value.atStartOfDay());
-    }
+import lombok.experimental.UtilityClass;
 
-    public static Date toDate(LocalDateTime value) {
-        return new Date(toEpochMilli(value));
-    }
+/**
+ * Model adapters.
+ */
+@UtilityClass
+public class WktModel {
 
-    public static Date toDate(OffsetDateTime value) {
-        return toDate(value.toLocalDateTime());
+    public <T> ComponentModel<T> toComponentModel(final IModel<T> model) {
+        return ComponentModel.lazy(model::getObject);
     }
 
-    // -- HELPER
-
-    private static final ZoneId zId = ZoneId.systemDefault();
-
-    private static long toEpochMilli(LocalDateTime localDateTime){
-        return localDateTime.atZone(zId).toInstant().toEpochMilli();
+    public <T> IModel<T> toIModel(final ComponentModel<T> model) {
+        return LambdaModel.of(model::get);
     }
 
 }
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/registries/components/ComponentFactoryRegistrarDefault.java b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/registries/components/ComponentFactoryRegistrarDefault.java
index 63103dcdea..9c216da2fd 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/registries/components/ComponentFactoryRegistrarDefault.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/causeway/viewer/wicket/viewer/registries/components/ComponentFactoryRegistrarDefault.java
@@ -27,6 +27,7 @@ import jakarta.inject.Inject;
 import jakarta.inject.Named;
 
 import org.apache.wicket.Component;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 import org.springframework.util.ClassUtils;
@@ -37,6 +38,7 @@ import org.apache.causeway.applib.value.semantics.ValueSemanticsResolver;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.internal.functions._Predicates;
+import org.apache.causeway.viewer.commons.applib.tabular.export.CollectionContentsExporter;
 import org.apache.causeway.viewer.wicket.model.models.ScalarModel;
 import org.apache.causeway.viewer.wicket.ui.ComponentFactory;
 import org.apache.causeway.viewer.wicket.ui.app.registry.ComponentFactoryRegistrar;
@@ -48,6 +50,7 @@ import org.apache.causeway.viewer.wicket.ui.components.actions.ActionParametersF
 import org.apache.causeway.viewer.wicket.ui.components.actions.ActionParametersPanelFactory;
 import org.apache.causeway.viewer.wicket.ui.components.bookmarkedpages.BookmarkedPagesPanelFactory;
 import org.apache.causeway.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory;
+import org.apache.causeway.viewer.wicket.ui.components.collectioncontents.export.CollectionContentsAsExportFactory;
 import org.apache.causeway.viewer.wicket.ui.components.collectioncontents.multiple.CollectionContentsMultipleViewsPanelFactory;
 import org.apache.causeway.viewer.wicket.ui.components.collectioncontents.summary.CollectionContentsAsSummaryFactory;
 import org.apache.causeway.viewer.wicket.ui.components.collectioncontents.unresolved.CollectionContentsHiddenPanelFactory;
@@ -100,6 +103,7 @@ import lombok.extern.log4j.Log4j2;
 public class ComponentFactoryRegistrarDefault implements ComponentFactoryRegistrar {
 
     private @Inject ValueSemanticsResolver valueSemanticsResolver;
+    private @Autowired(required = false) List<CollectionContentsExporter> collectionContentsExporters;
 
     private final List<ComponentFactory> componentFactoriesPluggedIn;
     public ComponentFactoryRegistrarDefault(final List<ComponentFactory> componentFactoriesPluggedIn) {
@@ -189,6 +193,10 @@ public class ComponentFactoryRegistrarDefault implements ComponentFactoryRegistr
     protected void addComponentFactoriesForEntityCollectionContents(final ComponentFactoryList componentFactories) {
         componentFactories.add(new CollectionContentsAsAjaxTablePanelFactory());
 
+        _NullSafe.stream(collectionContentsExporters)
+            .map(CollectionContentsAsExportFactory::new)
+            .forEach(componentFactories::add);
+
         // // work-in-progress
         // componentFactories.add(new CollectionContentsAsIconsPanelFactory());
 
@@ -294,8 +302,6 @@ public class ComponentFactoryRegistrarDefault implements ComponentFactoryRegistr
     public static class ScalarPanelFactoryForTextField<T extends Serializable>
     extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-        private static final long serialVersionUID = 1L;
-
         private final Class<T> valueTypeClass;
 
         protected ScalarPanelFactoryForTextField(final Class<T> valueTypeClass) {
@@ -313,8 +319,6 @@ public class ComponentFactoryRegistrarDefault implements ComponentFactoryRegistr
     public static class ScalarPanelFactoryForNumberField<T extends Serializable>
     extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-        private static final long serialVersionUID = 1L;
-
         private final Class<T> valueTypeClass;
 
         protected ScalarPanelFactoryForNumberField(final Class<T> valueTypeClass) {
@@ -331,8 +335,6 @@ public class ComponentFactoryRegistrarDefault implements ComponentFactoryRegistr
     public static class ScalarPanelFactoryForTemporalPicker<T extends Serializable>
     extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-        private static final long serialVersionUID = 1L;
-
         private final Class<T> valueTypeClass;
 
         protected ScalarPanelFactoryForTemporalPicker(final Class<T> valueTypeClass) {
@@ -351,8 +353,6 @@ public class ComponentFactoryRegistrarDefault implements ComponentFactoryRegistr
     public static class ScalarPanelFactoryForCompositeValue<T extends Serializable>
     extends ComponentFactoryScalarTypeConstrainedAbstract {
 
-        private static final long serialVersionUID = 1L;
-
         private final Class<T> valueTypeClass;
 
         protected ScalarPanelFactoryForCompositeValue(final Class<T> valueTypeClass) {