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

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


The following commit(s) were added to refs/heads/v4 by this push:
     new 0feaec87457 CAUSEWAY-3901: bit of refactoring; unable to fix download 
issue with long running blob action
0feaec87457 is described below

commit 0feaec8745728a0e5aa78f2a2e17d34374a48dd2
Author: Andi Huber <ahu...@apache.org>
AuthorDate: Fri Aug 29 13:49:49 2025 +0200

    CAUSEWAY-3901: bit of refactoring; unable to fix download issue with
    long running blob action
---
 .../components/widgets/actionlink/ActionLink.java  |  5 +-
 ...dHandlerFactory.java => LobRequestHandler.java} | 71 +++++++++++-----------
 .../causeway/viewer/wicket/ui/exec/Mediator.java   | 35 +++++------
 .../viewer/wicket/ui/exec/MediatorFactory.java     | 10 +--
 4 files changed, 53 insertions(+), 68 deletions(-)

diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/actionlink/ActionLink.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/actionlink/ActionLink.java
index 25a6d1b7229..1d09eba3ade 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/actionlink/ActionLink.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/widgets/actionlink/ActionLink.java
@@ -232,8 +232,9 @@ private void startDialogWithParams(final AjaxRequestTarget 
target) {
 
         castTo(ActionPromptWithExtraContent.class, actionPrompt)
         .ifPresent(promptWithExtraContent->{
-            
BSGridPanelFactory.extraContentForMixin(promptWithExtraContent.getExtraContentId(),
 actionModel)
-            
.ifPresent(gridPanel->promptWithExtraContent.setExtraContentPanel(gridPanel, 
target));
+            BSGridPanelFactory
+                
.extraContentForMixin(promptWithExtraContent.getExtraContentId(), actionModel)
+                
.ifPresent(gridPanel->promptWithExtraContent.setExtraContentPanel(gridPanel, 
target));
         });
     }
 
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/DownloadHandlerFactory.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/LobRequestHandler.java
similarity index 63%
rename from 
viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/DownloadHandlerFactory.java
rename to 
viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/LobRequestHandler.java
index 9eeca477cc1..134c1862487 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/DownloadHandlerFactory.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/LobRequestHandler.java
@@ -21,8 +21,10 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.Serializable;
 import java.time.Duration;
 
+import org.apache.wicket.request.IRequestCycle;
 import org.apache.wicket.request.IRequestHandler;
 import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
 import org.apache.wicket.request.resource.ContentDisposition;
@@ -30,32 +32,50 @@
 import org.apache.wicket.util.resource.IResourceStream;
 import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
 import org.apache.wicket.util.resource.StringResourceStream;
+import org.jspecify.annotations.Nullable;
 
 import org.apache.causeway.applib.value.Blob;
 import org.apache.causeway.applib.value.Clob;
 import org.apache.causeway.applib.value.NamedWithMimeType;
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
 
-import lombok.experimental.UtilityClass;
+public record LobRequestHandler(
+    NamedWithMimeType lob,
+    /**
+     * Duration for which the resource will be cached by the browser.
+     * Set to Duration.ZERO to disable browser caching.
+     */
+    @Nullable Duration cacheDuration) implements IRequestHandler, Serializable 
{
 
-@UtilityClass
-final class DownloadHandlerFactory {
-
-    public IRequestHandler downloadHandler(
+    public static LobRequestHandler downloadHandler(
             final ObjectAction action,
             final Object value) {
-        if(value instanceof Clob clob) {
-            return handlerFor(action, resourceStreamFor(clob), clob);
-        }
-        if(value instanceof Blob blob) {
-            return handlerFor(action, resourceStreamFor(blob), blob);
+        if(value instanceof NamedWithMimeType lob) {
+            return new LobRequestHandler(lob, 
action.getSemantics().isIdempotentOrCachable()
+                   ? null
+                   : Duration.ZERO);
         }
         return null;
     }
 
+    @Override
+    public void respond(IRequestCycle requestCycle) {
+        var handler = new ResourceStreamRequestHandler(
+            lob instanceof Blob blob
+                ? resourceStream(blob)
+                : lob instanceof Clob clob
+                    ? resourceStream(clob)
+                    : resourceStreamUnmatched(),
+            lob.name());
+        handler.setContentDisposition(ContentDisposition.ATTACHMENT);
+        handler.setCacheDuration(cacheDuration);
+        handler.respond(requestCycle);
+    }
+
     // -- HELPER
 
-    private IResourceStream resourceStreamFor(final Blob blob) {
+    private IResourceStream resourceStream(Blob blob) {
         final IResourceStream resourceStream = new AbstractResourceStream() {
             private static final long serialVersionUID = 1L;
             @Override public InputStream getInputStream() throws 
ResourceStreamNotFoundException {
@@ -70,35 +90,12 @@ private IResourceStream resourceStreamFor(final Blob blob) {
         return resourceStream;
     }
 
-    private IResourceStream resourceStreamFor(final Clob clob) {
+    private IResourceStream resourceStream(Clob clob) {
         return new StringResourceStream(clob.chars(), 
clob.mimeType().toString());
     }
 
-    private IRequestHandler handlerFor(
-            final ObjectAction action,
-            final IResourceStream resourceStream,
-            final NamedWithMimeType namedWithMimeType) {
-        var handler =
-                new ResourceStreamRequestHandler(resourceStream, 
namedWithMimeType.name());
-        handler.setContentDisposition(ContentDisposition.ATTACHMENT);
-
-        //CAUSEWAY-1619, prevent clients from caching the response content
-        return action.getSemantics().isIdempotentOrCachable()
-                ? handler
-                : enforceNoCacheOnClientSide(handler);
-    }
-
-    // -- CLIENT SIDE CACHING ASPECTS ...
-
-    private static IRequestHandler enforceNoCacheOnClientSide(final 
IRequestHandler downloadHandler){
-        if(downloadHandler==null) {
-            return downloadHandler;
-        }
-        if(downloadHandler instanceof ResourceStreamRequestHandler)
-            ((ResourceStreamRequestHandler) downloadHandler)
-            .setCacheDuration(Duration.ZERO);
-
-        return downloadHandler;
+    private IResourceStream resourceStreamUnmatched() {
+        throw _Exceptions.unmatchedCase(lob);
     }
 
 }
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/Mediator.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/Mediator.java
index b708cd3f019..428181c499a 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/Mediator.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/Mediator.java
@@ -18,21 +18,17 @@
  */
 package org.apache.causeway.viewer.wicket.ui.exec;
 
-import java.time.Duration;
-
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.behavior.AbstractAjaxBehavior;
 import org.apache.wicket.request.IRequestHandler;
 import org.apache.wicket.request.Url;
 import org.apache.wicket.request.cycle.RequestCycle;
-import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
-import org.apache.wicket.request.resource.ContentDisposition;
-import org.apache.wicket.util.resource.IResourceStream;
 import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
 
 import org.apache.causeway.applib.value.OpenUrlStrategy;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.commons.io.TextUtils;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.viewer.wicket.model.models.ActionModel;
@@ -129,20 +125,24 @@ void handle() {
             case SCHEDULE_HANDLER -> {
                 var requestCycle = RequestCycle.get();
                 var ajaxTarget = 
requestCycle.find(AjaxRequestTarget.class).orElse(null);
+                final IRequestHandler requestHandler = handler();
 
                 if (ajaxTarget == null) {
                     // non-Ajax request => just stream the Lob to the browser
                     // or if this is a no-arg action, there also will be no 
parent for the component
-                    requestCycle.scheduleRequestHandlerAfterCurrent(handler());
+                    
requestCycle.scheduleRequestHandlerAfterCurrent(requestHandler);
                     return;
                 }
                 // otherwise,
                 // Ajax request => respond with a redirect to be able to 
stream the Lob to the client
-                final IRequestHandler requestHandler = handler();
-                if(requestHandler instanceof ResourceStreamRequestHandler 
scheduledHandler) {
-                    var streamingBehavior = new 
StreamAfterAjaxResponseBehavior(scheduledHandler);
+                if(requestHandler instanceof LobRequestHandler 
lobRequestHandler) {
+                    var streamingBehavior = new 
StreamAfterAjaxResponseBehavior(lobRequestHandler);
                     ajaxTarget.getPage().add(streamingBehavior);
-                    scheduleJs(ajaxTarget, 
javascriptFor_sameWindow(streamingBehavior.getCallbackUrl()), 100);
+
+                    var relativeDownloadPageUri = 
TextUtils.cutter(streamingBehavior.getCallbackUrl().toString())
+                        .keepAfterLast("/")
+                        .getValue();
+                    scheduleJs(ajaxTarget, 
javascriptFor_sameWindow(relativeDownloadPageUri), 10);
                 } else if(requestHandler instanceof 
RedirectRequestHandlerWithOpenUrlStrategy redirectHandler) {
                     var fullUrl = expanded(requestCycle, 
redirectHandler.getRedirectUrl());
                     var js = redirectHandler.getOpenUrlStrategy().isNewWindow()
@@ -200,22 +200,15 @@ private static void scheduleJs(final AjaxRequestTarget 
target, final String js,
     private static class StreamAfterAjaxResponseBehavior extends 
AbstractAjaxBehavior {
         private static final long serialVersionUID = 1L;
 
-        private final String fileName;
-        private final IResourceStream resourceStream;
-        private final Duration cacheDuration;
+        private final LobRequestHandler lobRequestHandler;
 
-        public StreamAfterAjaxResponseBehavior(final 
ResourceStreamRequestHandler scheduledHandler) {
-            this.fileName = scheduledHandler.getFileName();
-            this.resourceStream = scheduledHandler.getResourceStream();
-            this.cacheDuration = scheduledHandler.getCacheDuration();
+        StreamAfterAjaxResponseBehavior(final LobRequestHandler 
lobRequestHandler) {
+            this.lobRequestHandler = lobRequestHandler;
         }
 
         @Override public void onRequest() {
-            var handler = new ResourceStreamRequestHandler(resourceStream, 
fileName);
-            handler.setCacheDuration(cacheDuration);
-            handler.setContentDisposition(ContentDisposition.ATTACHMENT);
             var page = getComponent();
-            page.getRequestCycle().scheduleRequestHandlerAfterCurrent(handler);
+            
page.getRequestCycle().scheduleRequestHandlerAfterCurrent(lobRequestHandler);
             page.remove(this);
         }
     }
diff --git 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/MediatorFactory.java
 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/MediatorFactory.java
index 71f1d451cbb..8de2add58a6 100644
--- 
a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/MediatorFactory.java
+++ 
b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/exec/MediatorFactory.java
@@ -135,16 +135,10 @@ private Mediator actionResultResponse(
             var pageRedirectRequest = 
PageRedirectRequest.forPage(ValuePage.class, valuePage);
             return Mediator.toPage(pageRedirectRequest);
         }
-        case VALUE_BLOB: {
-            final Object value = resultAdapter.getPojo();
-            IRequestHandler handler =
-                    
DownloadHandlerFactory.downloadHandler(actionModel.getAction(), value);
-            return Mediator.withHandler(handler);
-        }
+        case VALUE_BLOB:
         case VALUE_CLOB: {
             final Object value = resultAdapter.getPojo();
-            IRequestHandler handler =
-                    
DownloadHandlerFactory.downloadHandler(actionModel.getAction(), value);
+            IRequestHandler handler = 
LobRequestHandler.downloadHandler(actionModel.getAction(), value);
             return Mediator.withHandler(handler);
         }
         case VALUE_LOCALRESPATH_AJAX: {

Reply via email to