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

davsclaus pushed a commit to branch raw
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 27db86c407b00dc0f04cff914d4101838118d8ac
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Aug 21 10:17:58 2024 +0200

    CAMEL-21099: source dev console - Make it easier to download as files using 
raw mode
---
 .../platform/http/main/MainHttpServer.java         | 69 +++++++++++++------
 .../java/org/apache/camel/console/DevConsole.java  |  3 +-
 .../camel/impl/console/SourceDevConsole.java       | 80 ++++++++++++++++++++--
 .../camel/support/console/AbstractDevConsole.java  | 16 ++++-
 4 files changed, 141 insertions(+), 27 deletions(-)

diff --git 
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
 
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
index 6482f0abda3..fb7d8c90bd2 100644
--- 
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
+++ 
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
@@ -33,6 +33,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 import io.vertx.core.Handler;
@@ -442,7 +443,7 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
 
             @Override
             public void handle(RoutingContext ctx) {
-                ctx.response().putHeader("content-type", "application/json");
+                ctx.response().putHeader("Content-Type", "application/json");
 
                 JsonObject root = new JsonObject();
                 JsonObject jo = new JsonObject();
@@ -543,7 +544,7 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
         Handler<RoutingContext> handler = new Handler<RoutingContext>() {
             @Override
             public void handle(RoutingContext ctx) {
-                ctx.response().putHeader("content-type", "application/json");
+                ctx.response().putHeader("Content-Type", "application/json");
 
                 HealthCheckRegistry registry = 
HealthCheckRegistry.get(camelContext);
                 String level = ctx.request().getParam("exposureLevel");
@@ -740,10 +741,22 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
         devSub.method(HttpMethod.GET);
         devSub.produces("text/plain");
         devSub.produces("application/json");
+        devSub.produces("application/octet-stream");
 
         Handler<RoutingContext> handler = new Handler<RoutingContext>() {
             @Override
             public void handle(RoutingContext ctx) {
+                if (!camelContext.isDevConsole()) {
+                    ctx.response().putHeader("Content-Type", "text/plain");
+                    ctx.end("Developer Console is not enabled on CamelContext. 
Set camel.context.dev-console=true in application.properties");
+                }
+                DevConsoleRegistry dcr = 
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class);
+                if (dcr == null || !dcr.isEnabled()) {
+                    ctx.response().putHeader("Content-Type", "text/plain");
+                    ctx.end("Developer Console is not enabled");
+                    return;
+                }
+
                 String acp = ctx.request().getHeader("Accept");
                 int pos1 = acp != null ? acp.indexOf("html") : 
Integer.MAX_VALUE;
                 if (pos1 == -1) {
@@ -753,20 +766,13 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
                 if (pos2 == -1) {
                     pos2 = Integer.MAX_VALUE;
                 }
-                final boolean html = pos1 < pos2;
-                final boolean json = pos2 < pos1;
-                final DevConsole.MediaType mediaType = json ? 
DevConsole.MediaType.JSON : DevConsole.MediaType.TEXT;
-
-                ctx.response().putHeader("content-type", "text/plain");
-
-                if (!camelContext.isDevConsole()) {
-                    ctx.end("Developer Console is not enabled on CamelContext. 
Set camel.context.dev-console=true in application.properties");
-                }
-                DevConsoleRegistry dcr = 
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class);
-                if (dcr == null || !dcr.isEnabled()) {
-                    ctx.end("Developer Console is not enabled");
-                    return;
-                }
+                // special for download mode where we want to make it easy 
from a web-browser
+                final boolean download = 
"true".equals(ctx.request().getParam("download"));
+                final boolean raw = download || acp != null && 
acp.contains("application/octet-stream");
+                final boolean html = !raw && pos1 < pos2;
+                final boolean json = !raw && pos2 < pos1;
+                final DevConsole.MediaType mediaType
+                        = raw ? DevConsole.MediaType.RAW : json ? 
DevConsole.MediaType.JSON : DevConsole.MediaType.TEXT;
 
                 String path = StringHelper.after(ctx.request().path(), 
"/q/dev/");
                 String s = path;
@@ -805,11 +811,13 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
                     if (!sb.isEmpty()) {
                         String out = sb.toString();
                         if (html) {
-                            ctx.response().putHeader("content-type", 
"text/html");
+                            ctx.response().putHeader("Content-Type", 
"text/html");
+                        } else {
+                            ctx.response().putHeader("Content-Type", 
"text/plain");
                         }
                         ctx.end(out);
                     } else if (!root.isEmpty()) {
-                        ctx.response().putHeader("content-type", 
"application/json");
+                        ctx.response().putHeader("Content-Type", 
"application/json");
                         String out = root.toJson();
                         ctx.end(out);
                     } else {
@@ -821,6 +829,7 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
                     params.put(Exchange.HTTP_PATH, path);
                     StringBuilder sb = new StringBuilder();
                     JsonObject root = new JsonObject();
+                    final AtomicBoolean found = new AtomicBoolean();
 
                     // sort according to index by given id
                     dcr.stream().sorted((o1, o2) -> {
@@ -831,25 +840,45 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
                         boolean include = "all".equals(id) || 
c.getId().equalsIgnoreCase(id);
                         if (include && c.supportMediaType(mediaType)) {
                             Object out = c.call(mediaType, params);
+                            found.set(true);
                             if (out != null && mediaType == 
DevConsole.MediaType.TEXT) {
                                 sb.append(c.getDisplayName()).append(":");
                                 sb.append("\n\n");
                                 sb.append(out);
                                 sb.append("\n\n");
+                            } else if (out != null && mediaType == 
DevConsole.MediaType.RAW) {
+                                sb.append(out);
                             } else if (out != null && mediaType == 
DevConsole.MediaType.JSON) {
                                 root.put(c.getId(), out);
                             }
                         }
                     });
                     if (!sb.isEmpty()) {
+                        if (raw) {
+                            ctx.response().putHeader("Content-Type", 
"application/octet-stream");
+                            // in raw mode we may download files so put this 
into header information
+                            String disposition = (String) 
params.get("Content-Disposition");
+                            if (disposition != null) {
+                                
ctx.response().putHeader("Content-Disposition", disposition);
+                            }
+                        } else {
+                            ctx.response().putHeader("Content-Type", 
"text/plain");
+                        }
                         String out = sb.toString();
                         ctx.end(out);
                     } else if (!root.isEmpty()) {
-                        ctx.response().putHeader("content-type", 
"application/json");
+                        // root is json object
+                        ctx.response().putHeader("Content-Type", 
"application/json");
                         String out = root.toJson();
                         ctx.end(out);
                     } else {
-                        ctx.end("Developer Console not found: " + id);
+                        if (!found.get()) {
+                            ctx.response().putHeader("Content-Type", 
"text/plain");
+                            ctx.end("Developer Console not found: " + id);
+                        } else {
+                            ctx.response().setStatusCode(204);
+                            ctx.end();
+                        }
                     }
                 }
             }
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/console/DevConsole.java 
b/core/camel-api/src/main/java/org/apache/camel/console/DevConsole.java
index bdcdf069b6c..add78556a56 100644
--- a/core/camel-api/src/main/java/org/apache/camel/console/DevConsole.java
+++ b/core/camel-api/src/main/java/org/apache/camel/console/DevConsole.java
@@ -29,7 +29,8 @@ public interface DevConsole {
 
     enum MediaType {
         TEXT,
-        JSON
+        JSON,
+        RAW
     }
 
     /**
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SourceDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SourceDevConsole.java
index fd8aa2973ee..b02b232ce50 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SourceDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SourceDevConsole.java
@@ -21,6 +21,8 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 
 import org.apache.camel.Exchange;
@@ -55,16 +57,32 @@ public class SourceDevConsole extends AbstractDevConsole {
      */
     public static final String DUMP = "dump";
 
+    /**
+     * Whether to make downloading the source file easier from a web browser
+     */
+    public static final String DOWNLOAD = "download";
+
     public SourceDevConsole() {
         super("camel", "source", "Source", "Dump route source code");
     }
 
+    @Override
+    public boolean supportMediaType(MediaType mediaType) {
+        // also supports raw
+        return true;
+    }
+
     @Override
     protected String doCallText(Map<String, Object> options) {
         final StringBuilder sb = new StringBuilder();
 
-        boolean dump = "true".equals(options.getOrDefault(DUMP, "true"));
+        boolean download = "true".equals(options.getOrDefault(DOWNLOAD, 
"false"));
+        if (download) {
+            // use raw mode instead
+            return doCallRaw(options);
+        }
 
+        boolean dump = "true".equals(options.getOrDefault(DUMP, "true"));
         Function<ManagedRouteMBean, Object> task = mrb -> {
             String loc = mrb.getSourceLocation();
             if (loc != null) {
@@ -73,11 +91,10 @@ public class SourceDevConsole extends AbstractDevConsole {
                 try {
                     Resource resource = 
PluginHelper.getResourceLoader(getCamelContext()).resolveResource(loc);
                     if (resource != null) {
-                        if (!sb.isEmpty()) {
-                            sb.append("\n");
-                        }
-
                         if (dump) {
+                            if (!sb.isEmpty()) {
+                                sb.append("\n");
+                            }
                             LineNumberReader reader = new 
LineNumberReader(resource.getReader());
                             int i = 0;
                             String t;
@@ -143,6 +160,59 @@ public class SourceDevConsole extends AbstractDevConsole {
         return root;
     }
 
+    @Override
+    protected String doCallRaw(Map<String, Object> options) {
+        final StringBuilder sb = new StringBuilder();
+
+        boolean dump = "true".equals(options.getOrDefault(DUMP, "true"));
+        final AtomicInteger counter = new AtomicInteger();
+        final AtomicReference<String> name = new AtomicReference<>();
+
+        Function<ManagedRouteMBean, Object> task = mrb -> {
+            String loc = mrb.getSourceLocation();
+            if (loc != null) {
+                loc = LoggerHelper.stripSourceLocationLineNumber(loc);
+                StringBuilder code = new StringBuilder();
+                try {
+                    String onlyName = 
LoggerHelper.sourceNameOnly(mrb.getSourceLocation());
+                    Resource resource = 
PluginHelper.getResourceLoader(getCamelContext()).resolveResource(loc);
+                    if (resource != null) {
+                        if (dump) {
+                            // if we select only 1 file then remember the 
filename
+                            if (counter.incrementAndGet() == 1) {
+                                name.set(onlyName);
+                            } else {
+                                name.set(null);
+                            }
+                            if (!sb.isEmpty()) {
+                                sb.append("\n");
+                            }
+                            String text = 
IOHelper.loadText(resource.getInputStream());
+                            code.append(text);
+                        } else {
+                            // list of names
+                            sb.append(onlyName);
+                        }
+                    }
+                } catch (Exception e) {
+                    // ignore
+                }
+                if (!code.isEmpty()) {
+                    sb.append(code);
+                }
+            }
+            sb.append("\n");
+            return null;
+        };
+        doCall(options, task);
+
+        if (name.get() != null) {
+            options.put("Content-Disposition", String.format("attachment; 
filename=\"%s\"", name.get()));
+        }
+
+        return sb.toString();
+    }
+
     protected void doCall(Map<String, Object> options, 
Function<ManagedRouteMBean, Object> task) {
         String path = (String) options.get(Exchange.HTTP_PATH);
         String subPath = path != null ? StringHelper.after(path, "/") : null;
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/console/AbstractDevConsole.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/console/AbstractDevConsole.java
index c664c61d3b0..3d14a5050df 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/console/AbstractDevConsole.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/console/AbstractDevConsole.java
@@ -57,7 +57,8 @@ public abstract class AbstractDevConsole extends 
ServiceSupport implements DevCo
 
     @Override
     public boolean supportMediaType(MediaType mediaType) {
-        return true;
+        // text and json supported by default
+        return mediaType == MediaType.TEXT || mediaType == MediaType.JSON;
     }
 
     @Override
@@ -105,6 +106,8 @@ public abstract class AbstractDevConsole extends 
ServiceSupport implements DevCo
         try {
             if (mediaType == MediaType.JSON) {
                 return doCallJson(options);
+            } else if (mediaType == MediaType.RAW && 
supportMediaType(MediaType.RAW)) {
+                return doCallRaw(options);
             } else {
                 return doCallText(options);
             }
@@ -113,6 +116,17 @@ public abstract class AbstractDevConsole extends 
ServiceSupport implements DevCo
         }
     }
 
+    /**
+     * Invokes and gets the output from this console in raw format.
+     *
+     * The returned object can for example be binary data for file downloads
+     *
+     * @see DevConsole#call(MediaType, Map)
+     */
+    protected Object doCallRaw(Map<String, Object> options) {
+        return null;
+    }
+
     /**
      * Invokes and gets the output from this console in json format.
      *

Reply via email to