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.
*