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

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


The following commit(s) were added to refs/heads/main by this push:
     new 07f3ab882f9 dev console - source to be able to download using raw mode 
(#15248)
07f3ab882f9 is described below

commit 07f3ab882f9eca232322f6ceaf24e78f68643e66
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Aug 21 10:49:10 2024 +0200

    dev console - source to be able to download using raw mode (#15248)
    
    * CAMEL-21099: Fix filter in some dev consoles.
    
    * 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 +-
 .../apache/camel/impl/console/RouteDevConsole.java |   5 +-
 .../camel/impl/console/RouteDumpDevConsole.java    |   5 +-
 .../camel/impl/console/SourceDevConsole.java       | 131 +++++++++++++++++----
 .../apache/camel/impl/console/TopDevConsole.java   |   4 +-
 .../org/apache/camel/support/LoggerHelper.java     |   8 ++
 .../camel/support/console/AbstractDevConsole.java  |  16 ++-
 8 files changed, 194 insertions(+), 47 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/RouteDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
index 01358928afd..57df94a720f 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java
@@ -35,6 +35,7 @@ import org.apache.camel.api.management.ManagedCamelContext;
 import org.apache.camel.api.management.mbean.ManagedProcessorMBean;
 import org.apache.camel.api.management.mbean.ManagedRouteMBean;
 import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.LoggerHelper;
 import org.apache.camel.support.PatternHelper;
 import org.apache.camel.support.console.AbstractDevConsole;
 import org.apache.camel.util.StringHelper;
@@ -439,9 +440,11 @@ public class RouteDevConsole extends AbstractDevConsole {
             return true;
         }
 
+        String onlyName = LoggerHelper.sourceNameOnly(mrb.getSourceLocation());
         return PatternHelper.matchPattern(mrb.getRouteId(), filter)
                 || PatternHelper.matchPattern(mrb.getEndpointUri(), filter)
-                || PatternHelper.matchPattern(mrb.getSourceLocationShort(), 
filter);
+                || PatternHelper.matchPattern(mrb.getSourceLocationShort(), 
filter)
+                || PatternHelper.matchPattern(onlyName, filter);
     }
 
     private static int sort(ManagedRouteMBean o1, ManagedRouteMBean o2) {
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
index 65b46aeb838..aacea236c85 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
@@ -28,6 +28,7 @@ import org.apache.camel.Route;
 import org.apache.camel.api.management.ManagedCamelContext;
 import org.apache.camel.api.management.mbean.ManagedRouteMBean;
 import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.LoggerHelper;
 import org.apache.camel.support.PatternHelper;
 import org.apache.camel.support.console.AbstractDevConsole;
 import org.apache.camel.util.StringHelper;
@@ -166,9 +167,11 @@ public class RouteDumpDevConsole extends 
AbstractDevConsole {
             return true;
         }
 
+        String onlyName = LoggerHelper.sourceNameOnly(mrb.getSourceLocation());
         return PatternHelper.matchPattern(mrb.getRouteId(), filter)
                 || PatternHelper.matchPattern(mrb.getEndpointUri(), filter)
-                || PatternHelper.matchPattern(mrb.getSourceLocationShort(), 
filter);
+                || PatternHelper.matchPattern(mrb.getSourceLocationShort(), 
filter)
+                || PatternHelper.matchPattern(onlyName, filter);
     }
 
     private static int sort(ManagedRouteMBean o1, ManagedRouteMBean o2) {
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 82c7f57fbee..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;
@@ -50,13 +52,37 @@ public class SourceDevConsole extends AbstractDevConsole {
      */
     public static final String LIMIT = "limit";
 
+    /**
+     * Whether to dump or list file names only
+     */
+    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 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) {
@@ -65,21 +91,22 @@ public class SourceDevConsole extends AbstractDevConsole {
                 try {
                     Resource resource = 
PluginHelper.getResourceLoader(getCamelContext()).resolveResource(loc);
                     if (resource != null) {
-                        if (!sb.isEmpty()) {
-                            sb.append("\n");
-                        }
-
-                        LineNumberReader reader = new 
LineNumberReader(resource.getReader());
-                        int i = 0;
-                        String t;
-                        do {
-                            t = reader.readLine();
-                            if (t != null) {
-                                i++;
-                                code.append(String.format("\n    #%s %s", i, 
t));
+                        if (dump) {
+                            if (!sb.isEmpty()) {
+                                sb.append("\n");
                             }
-                        } while (t != null);
-                        IOHelper.close(reader);
+                            LineNumberReader reader = new 
LineNumberReader(resource.getReader());
+                            int i = 0;
+                            String t;
+                            do {
+                                t = reader.readLine();
+                                if (t != null) {
+                                    i++;
+                                    code.append(String.format("\n    #%s %s", 
i, t));
+                                }
+                            } while (t != null);
+                            IOHelper.close(reader);
+                        }
                     }
                 } catch (Exception e) {
                     // ignore
@@ -87,6 +114,7 @@ public class SourceDevConsole extends AbstractDevConsole {
                 sb.append(String.format("    Id: %s", mrb.getRouteId()));
                 if (mrb.getSourceLocation() != null) {
                     sb.append(String.format("\n    Source: %s", 
mrb.getSourceLocation()));
+                    sb.append(String.format("\n    File: %s", 
LoggerHelper.sourceNameOnly(loc)));
                 }
                 if (!code.isEmpty()) {
                     sb.append("\n");
@@ -103,6 +131,8 @@ public class SourceDevConsole extends AbstractDevConsole {
 
     @Override
     protected JsonObject doCallJson(Map<String, Object> options) {
+        boolean dump = "true".equals(options.getOrDefault(DUMP, "true"));
+
         final JsonObject root = new JsonObject();
         final List<JsonObject> list = new ArrayList<>();
 
@@ -112,14 +142,16 @@ public class SourceDevConsole extends AbstractDevConsole {
 
             jo.put("routeId", mrb.getRouteId());
             jo.put("from", mrb.getEndpointUri());
-            if (mrb.getSourceLocation() != null) {
-                jo.put("source", mrb.getSourceLocation());
-            }
-
             String loc = mrb.getSourceLocation();
-            List<JsonObject> code = 
ConsoleHelper.loadSourceAsJson(getCamelContext(), loc);
-            if (code != null) {
-                jo.put("code", code);
+            if (loc != null) {
+                jo.put("source", loc);
+                jo.put("file", LoggerHelper.sourceNameOnly(loc));
+                if (dump) {
+                    List<JsonObject> code = 
ConsoleHelper.loadSourceAsJson(getCamelContext(), loc);
+                    if (code != null) {
+                        jo.put("code", code);
+                    }
+                }
             }
             return null;
         };
@@ -128,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;
@@ -155,9 +240,11 @@ public class SourceDevConsole extends AbstractDevConsole {
             return true;
         }
 
+        String onlyName = LoggerHelper.sourceNameOnly(mrb.getSourceLocation());
         return PatternHelper.matchPattern(mrb.getRouteId(), filter)
                 || PatternHelper.matchPattern(mrb.getEndpointUri(), filter)
-                || PatternHelper.matchPattern(mrb.getSourceLocationShort(), 
filter);
+                || PatternHelper.matchPattern(mrb.getSourceLocationShort(), 
filter)
+                || PatternHelper.matchPattern(onlyName, filter);
     }
 
     private static int sort(ManagedRouteMBean o1, ManagedRouteMBean o2) {
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/TopDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/TopDevConsole.java
index 6bf8d32d321..82be71ac99f 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/TopDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/TopDevConsole.java
@@ -319,9 +319,11 @@ public class TopDevConsole extends AbstractDevConsole {
             return true;
         }
 
+        String onlyName = LoggerHelper.sourceNameOnly(mrb.getSourceLocation());
         return PatternHelper.matchPattern(mrb.getRouteId(), filter)
                 || PatternHelper.matchPattern(mrb.getEndpointUri(), filter)
-                || PatternHelper.matchPattern(mrb.getSourceLocationShort(), 
filter);
+                || PatternHelper.matchPattern(mrb.getSourceLocationShort(), 
filter)
+                || PatternHelper.matchPattern(onlyName, filter);
     }
 
     private static boolean acceptProcessor(ManagedProcessorMBean mpb, String 
filter) {
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/LoggerHelper.java 
b/core/camel-support/src/main/java/org/apache/camel/support/LoggerHelper.java
index a871604bdc7..b0495908de6 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/LoggerHelper.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/LoggerHelper.java
@@ -99,6 +99,14 @@ public final class LoggerHelper {
         }
     }
 
+    public static String stripScheme(String location) {
+        return StringHelper.after(location, ":", location);
+    }
+
+    public static String sourceNameOnly(String location) {
+        return stripScheme(stripSourceLocationLineNumber(location));
+    }
+
     public static Integer extractSourceLocationLineNumber(String location) {
         int cnt = StringHelper.countChar(location, ':');
         if (cnt > 1) {
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