You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/08/29 12:08:52 UTC
(camel) 01/01: CAMEL-24564: camel-tooling-model - Generate a static OpenAPI spec for all dev consoles and publish it in the camel-catalog
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch feature/CAMEL-24564-devconsole-openapi-catalog
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4ba2803df33ac3f9b5ed5ada8ea94aa6a5a8ddd6
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Aug 29 14:08:34 2026 +0200
CAMEL-24564: camel-tooling-model - Generate a static OpenAPI spec for all dev consoles and publish it in the camel-catalog
Extract the OpenAPI path/operation-building logic out of ApiDevConsole into a
new shared DevConsoleOpenApiHelper in camel-tooling-model, operating on the
existing DevConsoleModel/DevConsoleOptionModel typed beans (adding the
readOnly flag from CAMEL-24514 to that model and to JsonMapper's
generate/serialize methods, which it was previously missing).
ApiDevConsole now builds its live spec by parsing each console's catalog
schema via JsonMapper.generateDevConsoleModel() and delegating to the shared
helper, instead of hand-rolling JSON navigation - behavior-preserving.
PrepareCatalogMojo's executeDevConsoles() already parses every console's
catalog JSON into a DevConsoleModel while aggregating them into
camel-catalog; it now also builds a single consolidated
dev-consoles-openapi.json from those same models via the shared helper, with
zero additional parsing and no CamelContext dependency. Exposed via a new
CamelCatalog.devConsolesOpenApiSpec() method, mirroring the existing
mainJsonSchema()/jbangJsonSchema() raw-resource pattern.
Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
.../apache/camel/catalog/dev-consoles-openapi.json | 2489 ++++++++++++++++++++
.../org/apache/camel/catalog/CamelCatalog.java | 5 +
.../apache/camel/catalog/DefaultCamelCatalog.java | 5 +
.../org/apache/camel/catalog/CamelCatalogTest.java | 25 +
core/camel-console/pom.xml | 5 +
.../apache/camel/impl/console/ApiDevConsole.java | 183 +-
.../camel/tooling/model/DevConsoleModel.java | 9 +
.../tooling/model/DevConsoleOpenApiHelper.java | 160 ++
.../org/apache/camel/tooling/model/JsonMapper.java | 2 +
.../tooling/model/DevConsoleOpenApiHelperTest.java | 126 +
.../camel/maven/packaging/PrepareCatalogMojo.java | 12 +
11 files changed, 2858 insertions(+), 163 deletions(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
new file mode 100644
index 000000000000..6846a963fad3
--- /dev/null
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
@@ -0,0 +1,2489 @@
+{
+ "openapi": "3.0.3",
+ "info": {
+ "title": "Camel Dev Console API",
+ "version": "4.23.0-SNAPSHOT"
+ },
+ "paths": {
+ "\/q\/dev\/activity": {
+ "get": {
+ "summary": "Camel Activity",
+ "description": "Recent completed exchange activity",
+ "operationId": "activity",
+ "responses": {
+ "200": {
+ "description": "Camel Activity output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/api": {
+ "get": {
+ "summary": "API",
+ "description": "OpenAPI specification for the dev console API",
+ "operationId": "api",
+ "responses": {
+ "200": {
+ "description": "API output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/aws-secrets": {
+ "get": {
+ "summary": "AWS Secrets",
+ "description": "AWS Secrets Manager",
+ "operationId": "aws-secrets",
+ "responses": {
+ "200": {
+ "description": "AWS Secrets output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/aws2-s3": {
+ "get": {
+ "summary": "AWS S3",
+ "description": "AWS S3 Consumer",
+ "operationId": "aws2-s3",
+ "responses": {
+ "200": {
+ "description": "AWS S3 output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/azure-secrets": {
+ "get": {
+ "summary": "Azure Key Vault Secrets",
+ "description": "Azure Key Vault Secret Manager",
+ "operationId": "azure-secrets",
+ "responses": {
+ "200": {
+ "description": "Azure Key Vault Secrets output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/bean": {
+ "get": {
+ "summary": "Bean",
+ "description": "Displays Java beans from the registry",
+ "operationId": "bean",
+ "parameters": [
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the beans matching by name",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "internal",
+ "in": "query",
+ "description": "Whether to include internal Camel beans",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "nulls",
+ "in": "query",
+ "description": "Whether to include null values",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "properties",
+ "in": "query",
+ "description": "Whether to include bean properties",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Bean output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/bean-model": {
+ "get": {
+ "summary": "Bean Model",
+ "description": "Displays beans from the DSL model",
+ "operationId": "bean-model",
+ "parameters": [
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the beans matching by name",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "nulls",
+ "in": "query",
+ "description": "Whether to include null values",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "properties",
+ "in": "query",
+ "description": "Whether to include bean properties",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Bean Model output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/blocked": {
+ "get": {
+ "summary": "Blocked Exchanges",
+ "description": "Display blocked exchanges",
+ "operationId": "blocked",
+ "responses": {
+ "200": {
+ "description": "Blocked Exchanges output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/browse": {
+ "get": {
+ "summary": "Browse",
+ "description": "Browse pending messages on Camel components",
+ "operationId": "browse",
+ "parameters": [
+ {
+ "name": "bodyMaxChars",
+ "in": "query",
+ "description": "Maximum size of the message body to include in the dump",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 32768
+ }
+ },
+ {
+ "name": "dump",
+ "in": "query",
+ "description": "Whether to include message dumps",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the endpoints matching by route id, endpoint url",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "freshSize",
+ "in": "query",
+ "description": "Whether to calculate fresh queue size (can cause performance overhead)",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ {
+ "name": "includeBody",
+ "in": "query",
+ "description": "Whether to include message body in dumps",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of entries per endpoint",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 100
+ }
+ },
+ {
+ "name": "tail",
+ "in": "query",
+ "description": "To receive N last messages from the tail",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Browse output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/circuit-breaker": {
+ "get": {
+ "summary": "Circuit Breaker",
+ "description": "Display circuit breaker information",
+ "operationId": "circuit-breaker",
+ "responses": {
+ "200": {
+ "description": "Circuit Breaker output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/consumer": {
+ "get": {
+ "summary": "Consumers",
+ "description": "Display information about Camel consumers",
+ "operationId": "consumer",
+ "responses": {
+ "200": {
+ "description": "Consumers output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/context": {
+ "get": {
+ "summary": "CamelContext",
+ "description": "Overall information about the CamelContext",
+ "operationId": "context",
+ "responses": {
+ "200": {
+ "description": "CamelContext output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/datasource": {
+ "get": {
+ "summary": "DataSource",
+ "description": "Displays DataSource connection pool metrics",
+ "operationId": "datasource",
+ "responses": {
+ "200": {
+ "description": "DataSource output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/debug": {
+ "post": {
+ "summary": "Debug",
+ "description": "Camel route debugger",
+ "operationId": "debug",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "breakpoint": {
+ "type": "string",
+ "description": "The breakpoint node id"
+ },
+ "codeLimit": {
+ "type": "integer",
+ "default": 5,
+ "description": "Number of source code lines around the breakpoint to include"
+ },
+ "command": {
+ "type": "string",
+ "description": "Action command to execute on the debugger"
+ },
+ "history": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether to include message history"
+ },
+ "position": {
+ "type": "integer",
+ "description": "The position to step to"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Debug output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/endpoint": {
+ "get": {
+ "summary": "Endpoints",
+ "description": "Endpoint Registry information",
+ "operationId": "endpoint",
+ "responses": {
+ "200": {
+ "description": "Endpoints output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/errors": {
+ "get": {
+ "summary": "Error Registry",
+ "description": "Display captured routing errors",
+ "operationId": "errors",
+ "parameters": [
+ {
+ "name": "ago",
+ "in": "query",
+ "description": "Filter by time window as duration string (e.g. 60s, 5m, 1h). Only entries within this window are included.",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "exception",
+ "in": "query",
+ "description": "Filter by exception type (case-insensitive substring match)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "handled",
+ "in": "query",
+ "description": "Filter by handled status",
+ "required": false,
+ "schema": {
+ "type": "boolean"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of entries displayed",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "routeId",
+ "in": "query",
+ "description": "Filter by route id",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "stackTrace",
+ "in": "query",
+ "description": "Whether to include stack traces",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Error Registry output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/eval-language": {
+ "post": {
+ "summary": "Evaluate Language",
+ "description": "Evaluate Language and display result",
+ "operationId": "eval-language",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "body": {
+ "type": "string",
+ "description": "Optional message body"
+ },
+ "headers": {
+ "type": "string",
+ "description": "Optional message headers"
+ },
+ "language": {
+ "type": "string",
+ "default": "simple",
+ "description": "The language to use"
+ },
+ "predicate": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether to execute as predicate (use expression by default)"
+ },
+ "template": {
+ "type": "string",
+ "description": "Template to use for executing simple language function"
+ },
+ "variables": {
+ "type": "string",
+ "description": "Optional exchange variables"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Evaluate Language output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/event": {
+ "get": {
+ "summary": "Camel Events",
+ "description": "The most recent Camel events",
+ "operationId": "event",
+ "responses": {
+ "200": {
+ "description": "Camel Events output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/fault-tolerance": {
+ "get": {
+ "summary": "MicroProfile Circuit Breaker",
+ "description": "Display circuit breaker information",
+ "operationId": "fault-tolerance",
+ "responses": {
+ "200": {
+ "description": "MicroProfile Circuit Breaker output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/gc": {
+ "get": {
+ "summary": "Garbage Collector",
+ "description": "Displays Garbage Collector information",
+ "operationId": "gc",
+ "responses": {
+ "200": {
+ "description": "Garbage Collector output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/gcp-secrets": {
+ "get": {
+ "summary": "GCP Secrets",
+ "description": "GCP Secret Manager",
+ "operationId": "gcp-secrets",
+ "responses": {
+ "200": {
+ "description": "GCP Secrets output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/groovy": {
+ "get": {
+ "summary": "Groovy",
+ "description": "Groovy Language",
+ "operationId": "groovy",
+ "responses": {
+ "200": {
+ "description": "Groovy output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/hashicorp-secrets": {
+ "get": {
+ "summary": "HashiCorp Secrets",
+ "description": "HashiCorp Vault Secrets",
+ "operationId": "hashicorp-secrets",
+ "responses": {
+ "200": {
+ "description": "HashiCorp Secrets output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/health": {
+ "get": {
+ "summary": "Health Check",
+ "description": "Health Check Status",
+ "operationId": "health",
+ "parameters": [
+ {
+ "name": "exposureLevel",
+ "in": "query",
+ "description": "Exposure level for health check details",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "default": "default",
+ "enum": [
+ "default",
+ "oneline",
+ "full"
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Health Check output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/heap-dump": {
+ "post": {
+ "summary": "Heap Dump",
+ "description": "Write a heap dump (.hprof) file for deep memory analysis",
+ "operationId": "heap-dump",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "live": {
+ "type": "boolean",
+ "default": true,
+ "description": "Whether to dump only live objects (default true)"
+ },
+ "name": {
+ "type": "string",
+ "description": "File name for the heap dump (without .hprof extension)"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Heap Dump output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/heap-histogram": {
+ "get": {
+ "summary": "Heap Histogram",
+ "description": "Displays class-level heap memory usage",
+ "operationId": "heap-histogram",
+ "parameters": [
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of classes displayed",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Heap Histogram output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/ibm-secrets": {
+ "get": {
+ "summary": "IBM Secrets",
+ "description": "IBM Secrets Manager",
+ "operationId": "ibm-secrets",
+ "responses": {
+ "200": {
+ "description": "IBM Secrets output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/inflight": {
+ "get": {
+ "summary": "Inflight Exchanges",
+ "description": "Display inflight exchanges",
+ "operationId": "inflight",
+ "parameters": [
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the routes matching by route id, route uri",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of entries displayed",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Inflight Exchanges output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/internal-tasks": {
+ "get": {
+ "summary": "Internal Tasks",
+ "description": "Display information about internal tasks",
+ "operationId": "internal-tasks",
+ "responses": {
+ "200": {
+ "description": "Internal Tasks output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/java-security": {
+ "get": {
+ "summary": "Java Security",
+ "description": "Displays Java Security (JSSE) information",
+ "operationId": "java-security",
+ "responses": {
+ "200": {
+ "description": "Java Security output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/jfr": {
+ "post": {
+ "summary": "JFR Runtime Instrumentation",
+ "description": "Status and live control of camel-jfr runtime instrumentation",
+ "operationId": "jfr",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string",
+ "default": "status",
+ "enum": [
+ "status",
+ "enable",
+ "disable",
+ "jfc",
+ "snapshot"
+ ],
+ "description": "Command to perform"
+ },
+ "disable": {
+ "type": "string",
+ "description": "Comma separated list of events to disable in the generated jfc settings overlay"
+ },
+ "event": {
+ "type": "string",
+ "default": "all",
+ "enum": [
+ "all",
+ "route",
+ "processor",
+ "exchange",
+ "send",
+ "failed",
+ "redelivery"
+ ],
+ "description": "The runtime event to enable or disable, or all for every event"
+ },
+ "limit": {
+ "type": "integer",
+ "default": 50,
+ "description": "Maximum number of failure and redelivery entries to return in a snapshot"
+ },
+ "routeId": {
+ "type": "string",
+ "description": "Filter snapshot results to the given route id"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "JFR Runtime Instrumentation output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/jfr-memory-leak": {
+ "post": {
+ "summary": "JFR Memory Leak",
+ "description": "JFR-based old object sampling for memory leak diagnosis",
+ "operationId": "jfr-memory-leak",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string",
+ "default": "status",
+ "enum": [
+ "start",
+ "stop",
+ "status",
+ "query",
+ "compare"
+ ],
+ "description": "Command to execute"
+ },
+ "duration": {
+ "type": "integer",
+ "default": 0,
+ "description": "Recording duration in seconds (0 means manual stop)"
+ },
+ "limit": {
+ "type": "integer",
+ "default": 100,
+ "description": "Limits the number of entries displayed"
+ },
+ "minSize": {
+ "type": "integer",
+ "default": 0,
+ "description": "Minimum object size in bytes to include in results"
+ },
+ "stacktrace": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether to include stack traces in the output"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "JFR Memory Leak output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/jvm": {
+ "get": {
+ "summary": "JVM",
+ "description": "Displays JVM information",
+ "operationId": "jvm",
+ "responses": {
+ "200": {
+ "description": "JVM output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/kafka": {
+ "get": {
+ "summary": "Kafka",
+ "description": "Apache Kafka",
+ "operationId": "kafka",
+ "parameters": [
+ {
+ "name": "committed",
+ "in": "query",
+ "description": "Whether to include committed offset (sync operation to Kafka broker)",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Kafka output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/kubernetes-configmaps": {
+ "get": {
+ "summary": "Kubernetes Config Maps",
+ "description": "Kubernetes Cluster Config Maps",
+ "operationId": "kubernetes-configmaps",
+ "responses": {
+ "200": {
+ "description": "Kubernetes Config Maps output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/kubernetes-secrets": {
+ "get": {
+ "summary": "Kubernetes Secrets",
+ "description": "Kubernetes Cluster Secrets",
+ "operationId": "kubernetes-secrets",
+ "responses": {
+ "200": {
+ "description": "Kubernetes Secrets output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/log": {
+ "get": {
+ "summary": "Log",
+ "description": "Logging framework",
+ "operationId": "log",
+ "responses": {
+ "200": {
+ "description": "Log output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/main-configuration": {
+ "get": {
+ "summary": "Main Configuration",
+ "description": "Display Camel startup configuration",
+ "operationId": "main-configuration",
+ "responses": {
+ "200": {
+ "description": "Main Configuration output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/main-http-server": {
+ "get": {
+ "summary": "Main HTTP Server",
+ "description": "Camel Main HTTP Server",
+ "operationId": "main-http-server",
+ "responses": {
+ "200": {
+ "description": "Main HTTP Server output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/memory": {
+ "get": {
+ "summary": "JVM Memory",
+ "description": "Displays JVM memory information",
+ "operationId": "memory",
+ "responses": {
+ "200": {
+ "description": "JVM Memory output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/message-history": {
+ "get": {
+ "summary": "Message History",
+ "description": "History of latest completed exchange",
+ "operationId": "message-history",
+ "parameters": [
+ {
+ "name": "codeLimit",
+ "in": "query",
+ "description": "Number of source code lines around the location to include",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 5
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Message History output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/micrometer": {
+ "get": {
+ "summary": "Micrometer",
+ "description": "Display runtime metrics",
+ "operationId": "micrometer",
+ "parameters": [
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters matching metrics by name",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "tags",
+ "in": "query",
+ "description": "Whether to include tags",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Micrometer output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/opentelemetry": {
+ "get": {
+ "summary": "OpenTelemetry Spans",
+ "description": "OpenTelemetry span data captured in dev mode",
+ "operationId": "opentelemetry",
+ "parameters": [
+ {
+ "name": "dump",
+ "in": "query",
+ "description": "Whether to dump span data",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "true",
+ "false"
+ ]
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of spans dumped",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OpenTelemetry Spans output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/platform-http": {
+ "get": {
+ "summary": "Platform HTTP",
+ "description": "Embedded HTTP Server",
+ "operationId": "platform-http",
+ "responses": {
+ "200": {
+ "description": "Platform HTTP output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/processor": {
+ "post": {
+ "summary": "Processor",
+ "description": "Processor information",
+ "operationId": "processor",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": [
+ "start",
+ "stop",
+ "enable",
+ "disable"
+ ],
+ "description": "Action to perform such as start,stop,enable,disable on one or more processors"
+ },
+ "filter": {
+ "type": "string",
+ "description": "Filters the processors matching by processor id, route id, or route group, and source location"
+ },
+ "limit": {
+ "type": "integer",
+ "description": "Limits the number of entries displayed"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Processor output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/processor-detail": {
+ "get": {
+ "summary": "Processor Detail",
+ "description": "Show configured options for all processors in a route",
+ "operationId": "processor-detail",
+ "parameters": [
+ {
+ "name": "routeId",
+ "in": "query",
+ "description": "The route id to get processor details for (use * for all routes)",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Processor Detail output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/producer": {
+ "get": {
+ "summary": "Producers",
+ "description": "Display information about Camel producers",
+ "operationId": "producer",
+ "responses": {
+ "200": {
+ "description": "Producers output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/properties": {
+ "get": {
+ "summary": "Properties",
+ "description": "Displays the properties loaded by Camel",
+ "operationId": "properties",
+ "responses": {
+ "200": {
+ "description": "Properties output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/quartz": {
+ "get": {
+ "summary": "Quartz",
+ "description": "Quartz Scheduler",
+ "operationId": "quartz",
+ "responses": {
+ "200": {
+ "description": "Quartz output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/receive": {
+ "post": {
+ "summary": "Camel Receive",
+ "description": "Consume messages from endpoints",
+ "operationId": "receive",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "dump": {
+ "type": "string",
+ "enum": [
+ "true",
+ "false"
+ ],
+ "description": "Whether to dump received messages"
+ },
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "true",
+ "false"
+ ],
+ "description": "Whether to enable or disable receive mode"
+ },
+ "endpoint": {
+ "type": "string",
+ "description": "Endpoint for where to receive messages (can also refer to a route id, endpoint pattern)"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Camel Receive output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/reload": {
+ "post": {
+ "summary": "Reload",
+ "description": "Console for reloading running Camel",
+ "operationId": "reload",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "reload": {
+ "type": "boolean",
+ "default": false,
+ "description": "Option to trigger reloading"
+ },
+ "wait": {
+ "type": "boolean",
+ "default": false,
+ "description": "Option to wait for reloading to complete"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Reload output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/resilience4j": {
+ "get": {
+ "summary": "Resilience Circuit Breaker",
+ "description": "Display circuit breaker information",
+ "operationId": "resilience4j",
+ "responses": {
+ "200": {
+ "description": "Resilience Circuit Breaker output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/rest": {
+ "get": {
+ "summary": "Rest",
+ "description": "Rest DSL Registry information",
+ "operationId": "rest",
+ "responses": {
+ "200": {
+ "description": "Rest output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/rest-spec": {
+ "get": {
+ "summary": "Rest Spec",
+ "description": "OpenAPI specification content for contract-first REST services",
+ "operationId": "rest-spec",
+ "parameters": [
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters specifications matching the given URI pattern",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Rest Spec output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/route": {
+ "post": {
+ "summary": "Route",
+ "description": "Route information",
+ "operationId": "route",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": [
+ "start",
+ "stop",
+ "suspend",
+ "resume"
+ ],
+ "description": "Action to perform such as start,stop,suspend,resume on one or more routes"
+ },
+ "filter": {
+ "type": "string",
+ "description": "Filters the routes matching by route id, route uri, or route group, and source location"
+ },
+ "limit": {
+ "type": "integer",
+ "description": "Limits the number of entries displayed"
+ },
+ "processors": {
+ "type": "boolean",
+ "default": false,
+ "description": "Whether to include processors"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Route output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/route-controller": {
+ "get": {
+ "summary": "Route Controller",
+ "description": "Route controller information",
+ "operationId": "route-controller",
+ "parameters": [
+ {
+ "name": "error",
+ "in": "query",
+ "description": "Whether to include error details",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "stacktrace",
+ "in": "query",
+ "description": "Whether to include stack traces",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Route Controller output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/route-diagram": {
+ "get": {
+ "summary": "Route Diagram",
+ "description": "Visual route diagrams",
+ "operationId": "route-diagram",
+ "parameters": [
+ {
+ "name": "autoRefresh",
+ "in": "query",
+ "description": "Whether to auto-refresh page every 5 seconds",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "external",
+ "in": "query",
+ "description": "Whether to include external systems (kafka, http, etc.) in topology mode",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the routes matching by route id, route uri, and source location",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "default": "*"
+ }
+ },
+ {
+ "name": "fontSize",
+ "in": "query",
+ "description": "The size of the font",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 12
+ }
+ },
+ {
+ "name": "format",
+ "in": "query",
+ "description": "Output format: html (interactive web component), png (inline image), text or ascii (ASCII art), unicode (box-drawing characters)",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "default": "html",
+ "enum": [
+ "html",
+ "png",
+ "text",
+ "ascii",
+ "unicode"
+ ]
+ }
+ },
+ {
+ "name": "metric",
+ "in": "query",
+ "description": "Whether to include live metric counters",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
+ {
+ "name": "mode",
+ "in": "query",
+ "description": "Diagram mode: route or topology",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "default": "route",
+ "enum": [
+ "route",
+ "topology"
+ ]
+ }
+ },
+ {
+ "name": "nodeLabel",
+ "in": "query",
+ "description": "Node label mode",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "default": "code",
+ "enum": [
+ "code",
+ "description",
+ "both"
+ ]
+ }
+ },
+ {
+ "name": "nodeWidth",
+ "in": "query",
+ "description": "The node width in pixels",
+ "required": false,
+ "schema": {
+ "type": "integer",
+ "default": 180
+ }
+ },
+ {
+ "name": "theme",
+ "in": "query",
+ "description": "Theme to use: dark, light, transparent, ascii, or unicode",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "default": "transparent",
+ "enum": [
+ "dark",
+ "light",
+ "transparent",
+ "ascii",
+ "unicode"
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Route Diagram output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/route-dump": {
+ "get": {
+ "summary": "Route Dump",
+ "description": "Dump route in XML, YAML, or Java DSL format",
+ "operationId": "route-dump",
+ "parameters": [
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the routes matching by route id, route uri, and source location",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "format",
+ "in": "query",
+ "description": "To output in either xml, yaml, or java format",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "xml",
+ "yaml",
+ "java"
+ ]
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of entries displayed",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "uriAsParameters",
+ "in": "query",
+ "description": "Whether to expand URIs into separated key\/value parameters",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Route Dump output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/route-group": {
+ "post": {
+ "summary": "Route Group",
+ "description": "Route Group information",
+ "operationId": "route-group",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": [
+ "start",
+ "stop"
+ ],
+ "description": "Action to perform such as start or stop"
+ },
+ "filter": {
+ "type": "string",
+ "description": "Filters the route groups matching by group id"
+ },
+ "limit": {
+ "type": "integer",
+ "description": "Limits the number of entries displayed"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Route Group output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/route-structure": {
+ "get": {
+ "summary": "Route Structure",
+ "description": "Dump route structure",
+ "operationId": "route-structure",
+ "parameters": [
+ {
+ "name": "brief",
+ "in": "query",
+ "description": "Whether to dump in brief mode (only overall structure, and no detailed options or expressions)",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the routes matching by route id, route uri, and source location",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of entries displayed",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "metric",
+ "in": "query",
+ "description": "Whether to include metrics such as number of messages processed (from JMX)",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Route Structure output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/route-topology": {
+ "get": {
+ "summary": "Route Topology",
+ "description": "Route topology showing inter-route connections",
+ "operationId": "route-topology",
+ "parameters": [
+ {
+ "name": "external",
+ "in": "query",
+ "description": "Whether to include external systems (databases, messaging brokers, etc.) as nodes",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ {
+ "name": "metric",
+ "in": "query",
+ "description": "Whether to include live metrics (message counts) on nodes and edges",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ {
+ "name": "routes",
+ "in": "query",
+ "description": "Whether to include route structure data in the response",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Route Topology output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/send": {
+ "post": {
+ "summary": "Camel Send",
+ "description": "Send messages to endpoints",
+ "operationId": "send",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "body": {
+ "type": "string",
+ "description": "The message body to send. Can refer to files using file: prefix"
+ },
+ "bodyMaxChars": {
+ "type": "integer",
+ "default": 32768,
+ "description": "Maximum size of the message body to include in the dump"
+ },
+ "endpoint": {
+ "type": "string",
+ "description": "Endpoint for where to send messages (can also refer to a route id, endpoint pattern)"
+ },
+ "exchangePattern": {
+ "type": "string",
+ "enum": [
+ "InOnly",
+ "InOut"
+ ],
+ "description": "Exchange pattern when sending"
+ },
+ "poll": {
+ "type": "boolean",
+ "description": "Whether to poll message from the endpoint instead of sending"
+ },
+ "pollTimeout": {
+ "type": "integer",
+ "default": 20000,
+ "description": "Timeout when using poll mode"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Camel Send output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/service": {
+ "get": {
+ "summary": "Services",
+ "description": "Services used for network communication with clients",
+ "operationId": "service",
+ "responses": {
+ "200": {
+ "description": "Services output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/sftp": {
+ "get": {
+ "summary": "SFTP",
+ "description": "Secure FTP using JSCH",
+ "operationId": "sftp",
+ "responses": {
+ "200": {
+ "description": "SFTP output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/simple-language": {
+ "get": {
+ "summary": "Simple Language",
+ "description": "Display simple language details",
+ "operationId": "simple-language",
+ "responses": {
+ "200": {
+ "description": "Simple Language output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/source": {
+ "get": {
+ "summary": "Source",
+ "description": "Dump route source code",
+ "operationId": "source",
+ "parameters": [
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the routes matching by route id, route uri, and source location",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of entries displayed",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Source output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/sql-query": {
+ "post": {
+ "summary": "SQL Query",
+ "description": "Execute SQL queries on DataSource beans",
+ "operationId": "sql-query",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "actionType": {
+ "type": "string",
+ "default": "query",
+ "enum": [
+ "query",
+ "update-row"
+ ],
+ "description": "Action type: query (default) or update-row"
+ },
+ "columnValues": {
+ "type": "string",
+ "description": "Changed column-value pairs as JSON string for update-row action"
+ },
+ "datasource": {
+ "type": "string",
+ "description": "Name of the DataSource bean in the registry (auto-detected if only one exists)"
+ },
+ "maxRows": {
+ "type": "integer",
+ "default": 100,
+ "description": "Maximum number of rows to return"
+ },
+ "primaryKeyValues": {
+ "type": "string",
+ "description": "Primary key column-value pairs as JSON string for update-row action"
+ },
+ "queryTimeout": {
+ "type": "integer",
+ "default": 30,
+ "description": "Query timeout in seconds"
+ },
+ "sql": {
+ "type": "string",
+ "description": "The SQL query to execute"
+ },
+ "table": {
+ "type": "string",
+ "description": "Table name for update-row action"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "SQL Query output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/sql-trace": {
+ "get": {
+ "summary": "SQL Trace",
+ "description": "Trace SQL query executions",
+ "operationId": "sql-trace",
+ "responses": {
+ "200": {
+ "description": "SQL Trace output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/startup-recorder": {
+ "get": {
+ "summary": "Startup Recorder",
+ "description": "Starting recording information",
+ "operationId": "startup-recorder",
+ "responses": {
+ "200": {
+ "description": "Startup Recorder output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/stub": {
+ "get": {
+ "summary": "Stub",
+ "description": "Browse messages on stub endpoints",
+ "operationId": "stub",
+ "parameters": [
+ {
+ "name": "browse",
+ "in": "query",
+ "description": "Whether to browse messages",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the routes matching by queue name",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "format",
+ "in": "query",
+ "description": "To use either xml or json output format",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "xml",
+ "json"
+ ]
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of messages dumped",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Stub output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/system-properties": {
+ "get": {
+ "summary": "System Properties",
+ "description": "Displays Java System Properties",
+ "operationId": "system-properties",
+ "responses": {
+ "200": {
+ "description": "System Properties output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/thread": {
+ "get": {
+ "summary": "Thread",
+ "description": "Displays JVM Threads information",
+ "operationId": "thread",
+ "parameters": [
+ {
+ "name": "stackTrace",
+ "in": "query",
+ "description": "Whether to include thread stack traces",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": false
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Thread output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/top": {
+ "get": {
+ "summary": "Top Routes",
+ "description": "Display the top routes",
+ "operationId": "top",
+ "parameters": [
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Filters the routes and processors matching by route id, route uri, processor id, and source location",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Limits the number of entries displayed",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Top Routes output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/trace": {
+ "post": {
+ "summary": "Camel Tracing",
+ "description": "Trace routed messages",
+ "operationId": "trace",
+ "requestBody": {
+ "content": {
+ "application\/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "dump": {
+ "type": "string",
+ "enum": [
+ "true",
+ "false"
+ ],
+ "description": "Whether to dump trace messages"
+ },
+ "enabled": {
+ "type": "string",
+ "enum": [
+ "true",
+ "false"
+ ],
+ "description": "Whether to enable or disable tracing"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Camel Tracing output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/transformers": {
+ "get": {
+ "summary": "Data Type Transformers",
+ "description": "Data-type transformer information",
+ "operationId": "transformers",
+ "responses": {
+ "200": {
+ "description": "Data Type Transformers output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/type-converters": {
+ "get": {
+ "summary": "Type Converters",
+ "description": "Camel Type Converter information",
+ "operationId": "type-converters",
+ "responses": {
+ "200": {
+ "description": "Type Converters output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/variables": {
+ "get": {
+ "summary": "Variables",
+ "description": "Displays variables",
+ "operationId": "variables",
+ "responses": {
+ "200": {
+ "description": "Variables output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ },
+ "\/q\/dev\/vertx-websocket": {
+ "get": {
+ "summary": "Vert.x WebSocket",
+ "description": "Vert.x WebSocket consumer details",
+ "operationId": "vertx-websocket",
+ "parameters": [
+ {
+ "name": "includeHeaders",
+ "in": "query",
+ "description": "Whether to include WebSocket peer connection header details in the output",
+ "required": false,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Vert.x WebSocket output",
+ "content": {
+ "application\/json": {
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java b/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java
index e6da89abff48..d71a426840bc 100644
--- a/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java
+++ b/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/CamelCatalog.java
@@ -599,6 +599,11 @@ public interface CamelCatalog {
*/
String listDevConsolesAsJson();
+ /**
+ * The OpenAPI 3.0 specification describing every dev console endpoint, aggregated across all dev consoles.
+ */
+ String devConsolesOpenApiSpec();
+
/**
* Lists all the models (EIPs) summary details in JSon
*/
diff --git a/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultCamelCatalog.java b/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultCamelCatalog.java
index 8bb8ba3d8d83..67d0a9c19138 100644
--- a/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultCamelCatalog.java
+++ b/catalog/camel-catalog/src/main/java/org/apache/camel/catalog/DefaultCamelCatalog.java
@@ -546,6 +546,11 @@ public class DefaultCamelCatalog extends AbstractCachingCamelCatalog implements
.toList()));
}
+ @Override
+ public String devConsolesOpenApiSpec() {
+ return cache(BASE_RESOURCE_DIR + "/dev-consoles-openapi.json", this::loadResource);
+ }
+
@Override
public String listModelsAsJson() {
return cache(LIST_MODELS_AS_JSON, () -> JsonMapper.serialize(findModelNames().stream()
diff --git a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
index f5217b7dbe86..dda65723921c 100644
--- a/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
+++ b/catalog/camel-catalog/src/test/java/org/apache/camel/catalog/CamelCatalogTest.java
@@ -38,6 +38,7 @@ import org.apache.camel.tooling.model.LanguageModel;
import org.apache.camel.tooling.model.PojoBeanModel;
import org.apache.camel.tooling.model.ReleaseModel;
import org.apache.camel.tooling.model.SecurityAdvisoryModel;
+import org.apache.camel.util.json.JsonObject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -1772,6 +1773,30 @@ public class CamelCatalogTest {
Assertions.assertTrue(advisory.getComponents().contains("camel-bean"));
}
+ @Test
+ public void devConsolesOpenApiSpec() {
+ String json = catalog.devConsolesOpenApiSpec();
+ Assertions.assertNotNull(json);
+
+ JsonObject doc = JsonMapper.deserialize(json);
+ Assertions.assertEquals("3.0.3", doc.getString("openapi"));
+
+ JsonObject paths = doc.getJsonObject("paths");
+ Assertions.assertNotNull(paths);
+
+ JsonObject context = paths.getJsonObject("/q/dev/context");
+ Assertions.assertNotNull(context);
+ Assertions.assertNotNull(context.getJsonObject("get"));
+ Assertions.assertNull(context.get("post"));
+
+ JsonObject route = paths.getJsonObject("/q/dev/route");
+ Assertions.assertNotNull(route);
+ Assertions.assertNull(route.get("get"));
+ JsonObject post = route.getJsonObject("post");
+ Assertions.assertNotNull(post);
+ Assertions.assertNotNull(post.getJsonObject("requestBody"));
+ }
+
@Test
public void testFindPojoBeanNames() {
List<String> names = catalog.findBeansNames();
diff --git a/core/camel-console/pom.xml b/core/camel-console/pom.xml
index c09ab7f98d73..e6f22770d58a 100644
--- a/core/camel-console/pom.xml
+++ b/core/camel-console/pom.xml
@@ -49,6 +49,11 @@
<groupId>org.apache.camel</groupId>
<artifactId>camel-util-json</artifactId>
</dependency>
+ <!-- tooling model (for building the api console's OpenAPI spec) -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-tooling-model</artifactId>
+ </dependency>
<!-- testing -->
<dependency>
diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/ApiDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/ApiDevConsole.java
index 2fe27561dda6..2b1cc6b1c101 100644
--- a/core/camel-console/src/main/java/org/apache/camel/impl/console/ApiDevConsole.java
+++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/ApiDevConsole.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.impl.console;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -24,7 +25,9 @@ import org.apache.camel.console.DevConsoleRegistry;
import org.apache.camel.spi.Configurer;
import org.apache.camel.spi.annotations.DevConsole;
import org.apache.camel.support.console.AbstractDevConsole;
-import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.tooling.model.DevConsoleModel;
+import org.apache.camel.tooling.model.DevConsoleOpenApiHelper;
+import org.apache.camel.tooling.model.JsonMapper;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
@@ -63,183 +66,37 @@ public class ApiDevConsole extends AbstractDevConsole {
}
private String buildOpenApi() {
- JsonObject root = new JsonObject();
- root.put("openapi", "3.0.3");
-
- JsonObject info = new JsonObject();
- info.put("title", "Camel Dev Console API");
- info.put("version", getCamelContext().getVersion());
- root.put("info", info);
-
- JsonObject paths = new JsonObject();
+ List<DevConsoleModel> models = new ArrayList<>();
DevConsoleRegistry dcr = getCamelContext().getCamelContextExtension()
.getContextPlugin(DevConsoleRegistry.class);
if (dcr != null && dcr.isEnabled()) {
- List<org.apache.camel.console.DevConsole> consoles = dcr.stream()
- .sorted((a, b) -> a.getId().compareToIgnoreCase(b.getId()))
- .toList();
-
- for (org.apache.camel.console.DevConsole console : consoles) {
- String id = console.getId();
- boolean readOnly = console.isReadOnly();
-
- JsonObject pathItem = new JsonObject();
- JsonObject operation = new JsonObject();
- operation.put("summary", console.getDisplayName());
- operation.put("description", console.getDescription());
- operation.put("operationId", id);
-
- if (readOnly) {
- JsonArray parameters = buildConsoleParameters(id);
- if (parameters != null) {
- operation.put("parameters", parameters);
- }
- } else {
- JsonObject requestBody = buildConsoleRequestBody(id);
- if (requestBody != null) {
- operation.put("requestBody", requestBody);
- }
- }
-
- JsonObject responses = new JsonObject();
- JsonObject ok = new JsonObject();
- ok.put("description", console.getDisplayName() + " output");
- JsonObject responseContent = new JsonObject();
- responseContent.put("application/json", new JsonObject());
- ok.put("content", responseContent);
- responses.put("200", ok);
- operation.put("responses", responses);
-
- pathItem.put(readOnly ? "get" : "post", operation);
- paths.put("/q/dev/" + id, pathItem);
+ for (org.apache.camel.console.DevConsole console : dcr.stream().toList()) {
+ models.add(buildConsoleModel(console));
}
}
- root.put("paths", paths);
+ JsonObject root = DevConsoleOpenApiHelper.buildOpenApiDocument(models, getCamelContext().getVersion());
return Jsoner.prettyPrint(root.toJson());
}
/**
- * Loads and parses the option schema for the given console.
- *
- * @return the parsed options, or null if the console has no options or the schema could not be loaded
+ * Builds the {@link DevConsoleModel} for the given live console: its options are loaded from the console's
+ * generated catalog schema, while id/displayName/description/readOnly come from the live instance.
*/
- private JsonObject loadConsoleOptions(String consoleId) {
+ private DevConsoleModel buildConsoleModel(org.apache.camel.console.DevConsole console) {
+ DevConsoleModel model;
try {
String json = ((CatalogCamelContext) getCamelContext())
- .getDevConsoleParameterJsonSchema(consoleId);
- if (json == null) {
- return null;
- }
- Object parsed = Jsoner.deserialize(json);
- if (!(parsed instanceof JsonObject jo)) {
- return null;
- }
- Object optionsObj = jo.get("options");
- if (!(optionsObj instanceof JsonObject opts) || opts.isEmpty()) {
- return null;
- }
- return opts;
+ .getDevConsoleParameterJsonSchema(console.getId());
+ model = json != null ? JsonMapper.generateDevConsoleModel(json) : new DevConsoleModel();
} catch (Exception e) {
- // ignore
- return null;
- }
- }
-
- private JsonObject buildConsoleRequestBody(String consoleId) {
- JsonObject opts = loadConsoleOptions(consoleId);
- if (opts == null) {
- return null;
- }
-
- JsonObject properties = new JsonObject();
- JsonArray required = new JsonArray();
- for (Map.Entry<String, Object> entry : opts.entrySet()) {
- String name = entry.getKey();
- if (!(entry.getValue() instanceof JsonObject opt)) {
- continue;
- }
- JsonObject prop = new JsonObject();
- String type = opt.getString("type");
- if (type != null) {
- prop.put("type", type);
- }
- String description = opt.getString("description");
- if (description != null) {
- prop.put("description", description);
- }
- Object defaultValue = opt.get("defaultValue");
- if (defaultValue != null) {
- prop.put("default", defaultValue);
- }
- Object enumValues = opt.get("enum");
- if (enumValues instanceof JsonArray ea && !ea.isEmpty()) {
- prop.put("enum", enumValues);
- }
- properties.put(name, prop);
-
- Boolean req = opt.getBoolean("required");
- if (req != null && req) {
- required.add(name);
- }
- }
-
- JsonObject schema = new JsonObject();
- schema.put("type", "object");
- schema.put("properties", properties);
- if (!required.isEmpty()) {
- schema.put("required", required);
- }
-
- JsonObject mediaType = new JsonObject();
- mediaType.put("schema", schema);
- JsonObject content = new JsonObject();
- content.put("application/json", mediaType);
- JsonObject requestBody = new JsonObject();
- requestBody.put("content", content);
- return requestBody;
- }
-
- private JsonArray buildConsoleParameters(String consoleId) {
- JsonObject opts = loadConsoleOptions(consoleId);
- if (opts == null) {
- return null;
- }
-
- JsonArray parameters = new JsonArray();
- for (Map.Entry<String, Object> entry : opts.entrySet()) {
- String name = entry.getKey();
- if (!(entry.getValue() instanceof JsonObject opt)) {
- continue;
- }
- JsonObject param = new JsonObject();
- param.put("name", name);
- param.put("in", "query");
- String description = opt.getString("description");
- if (description != null) {
- param.put("description", description);
- }
- Boolean req = opt.getBoolean("required");
- param.put("required", req != null && req);
-
- JsonObject schema = new JsonObject();
- String type = opt.getString("type");
- if (type != null) {
- schema.put("type", type);
- }
- Object defaultValue = opt.get("defaultValue");
- if (defaultValue != null) {
- schema.put("default", defaultValue);
- }
- Object enumValues = opt.get("enum");
- if (enumValues instanceof JsonArray ea && !ea.isEmpty()) {
- schema.put("enum", enumValues);
- }
- param.put("schema", schema);
-
- parameters.add(param);
+ model = new DevConsoleModel();
}
- return parameters.isEmpty() ? null : parameters;
+ model.setName(console.getId());
+ model.setTitle(console.getDisplayName());
+ model.setDescription(console.getDescription());
+ model.setReadOnly(console.isReadOnly());
+ return model;
}
}
diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleModel.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleModel.java
index 48eea3fabc1a..74a4afa5ee0f 100644
--- a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleModel.java
+++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleModel.java
@@ -19,6 +19,7 @@ package org.apache.camel.tooling.model;
public class DevConsoleModel extends ArtifactModel<DevConsoleModel.DevConsoleOptionModel> {
protected String group;
+ protected boolean readOnly = true;
public DevConsoleModel() {
}
@@ -31,6 +32,14 @@ public class DevConsoleModel extends ArtifactModel<DevConsoleModel.DevConsoleOpt
this.group = group;
}
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
@Override
public Kind getKind() {
return Kind.console;
diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelper.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelper.java
new file mode 100644
index 000000000000..4bbdbd40e855
--- /dev/null
+++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelper.java
@@ -0,0 +1,160 @@
+/*
+ * 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.camel.tooling.model;
+
+import java.util.List;
+
+import org.apache.camel.tooling.model.DevConsoleModel.DevConsoleOptionModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * Builds an OpenAPI 3.0 specification describing the dev console {@code /q/dev/{id}} endpoints, from
+ * {@link DevConsoleModel} instances. Shared between the live {@code api} dev console (which sources models from a
+ * running {@code DevConsoleRegistry}) and the build-time catalog generator (which sources models from the aggregated
+ * catalog {@code dev-console/*.json} files), so both produce identical output.
+ */
+public final class DevConsoleOpenApiHelper {
+
+ private DevConsoleOpenApiHelper() {
+ }
+
+ /**
+ * Builds a full OpenAPI document for the given consoles.
+ */
+ public static JsonObject buildOpenApiDocument(List<DevConsoleModel> consoles, String version) {
+ JsonObject root = new JsonObject();
+ root.put("openapi", "3.0.3");
+
+ JsonObject info = new JsonObject();
+ info.put("title", "Camel Dev Console API");
+ info.put("version", version);
+ root.put("info", info);
+
+ JsonObject paths = new JsonObject();
+ consoles.stream()
+ .sorted((a, b) -> a.getName().compareToIgnoreCase(b.getName()))
+ .forEach(model -> paths.put("/q/dev/" + model.getName(), buildPathItem(model)));
+ root.put("paths", paths);
+
+ return root;
+ }
+
+ /**
+ * Builds the OpenAPI path item ({@code get} or {@code post} operation) for a single console.
+ */
+ public static JsonObject buildPathItem(DevConsoleModel model) {
+ JsonObject operation = new JsonObject();
+ operation.put("summary", model.getTitle());
+ operation.put("description", model.getDescription());
+ operation.put("operationId", model.getName());
+
+ boolean readOnly = model.isReadOnly();
+ if (readOnly) {
+ JsonArray parameters = buildParameters(model.getOptions());
+ if (parameters != null) {
+ operation.put("parameters", parameters);
+ }
+ } else {
+ JsonObject requestBody = buildRequestBody(model.getOptions());
+ if (requestBody != null) {
+ operation.put("requestBody", requestBody);
+ }
+ }
+
+ JsonObject responses = new JsonObject();
+ JsonObject ok = new JsonObject();
+ ok.put("description", model.getTitle() + " output");
+ JsonObject responseContent = new JsonObject();
+ responseContent.put("application/json", new JsonObject());
+ ok.put("content", responseContent);
+ responses.put("200", ok);
+ operation.put("responses", responses);
+
+ JsonObject pathItem = new JsonObject();
+ pathItem.put(readOnly ? "get" : "post", operation);
+ return pathItem;
+ }
+
+ private static JsonArray buildParameters(List<DevConsoleOptionModel> options) {
+ if (options == null || options.isEmpty()) {
+ return null;
+ }
+
+ JsonArray parameters = new JsonArray();
+ for (DevConsoleOptionModel opt : options) {
+ JsonObject param = new JsonObject();
+ param.put("name", opt.getName());
+ param.put("in", "query");
+ if (opt.getDescription() != null) {
+ param.put("description", opt.getDescription());
+ }
+ param.put("required", opt.isRequired());
+ param.put("schema", buildSchema(opt));
+ parameters.add(param);
+ }
+ return parameters;
+ }
+
+ private static JsonObject buildRequestBody(List<DevConsoleOptionModel> options) {
+ if (options == null || options.isEmpty()) {
+ return null;
+ }
+
+ JsonObject properties = new JsonObject();
+ JsonArray required = new JsonArray();
+ for (DevConsoleOptionModel opt : options) {
+ JsonObject prop = buildSchema(opt);
+ if (opt.getDescription() != null) {
+ prop.put("description", opt.getDescription());
+ }
+ properties.put(opt.getName(), prop);
+ if (opt.isRequired()) {
+ required.add(opt.getName());
+ }
+ }
+
+ JsonObject schema = new JsonObject();
+ schema.put("type", "object");
+ schema.put("properties", properties);
+ if (!required.isEmpty()) {
+ schema.put("required", required);
+ }
+
+ JsonObject mediaType = new JsonObject();
+ mediaType.put("schema", schema);
+ JsonObject content = new JsonObject();
+ content.put("application/json", mediaType);
+ JsonObject requestBody = new JsonObject();
+ requestBody.put("content", content);
+ return requestBody;
+ }
+
+ private static JsonObject buildSchema(DevConsoleOptionModel opt) {
+ JsonObject schema = new JsonObject();
+ if (opt.getType() != null) {
+ schema.put("type", opt.getType());
+ }
+ if (opt.getDefaultValue() != null) {
+ schema.put("default", opt.resolveDefaultValue());
+ }
+ if (opt.getEnums() != null && !opt.getEnums().isEmpty()) {
+ schema.put("enum", new JsonArray(opt.getEnums()));
+ }
+ return schema;
+ }
+}
diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
index 40723b4e4acb..46903a93bd7b 100644
--- a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
+++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
@@ -494,6 +494,7 @@ public final class JsonMapper {
DevConsoleModel model = new DevConsoleModel();
parseModel(mobj, model);
model.setGroup(mobj.getString("group"));
+ model.setReadOnly(mobj.getBooleanOrDefault("readOnly", true));
parseArtifact(mobj, model);
JsonObject options = (JsonObject) obj.get("options");
if (options != null) {
@@ -517,6 +518,7 @@ public final class JsonMapper {
baseToJson(model, obj);
artifactToJson(model, obj);
obj.put("group", model.getGroup());
+ obj.put("readOnly", model.isReadOnly());
obj.entrySet().removeIf(e -> e.getValue() == null);
JsonObject wrapper = new JsonObject();
wrapper.put("console", obj);
diff --git a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelperTest.java b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelperTest.java
new file mode 100644
index 000000000000..f92b673b1227
--- /dev/null
+++ b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/DevConsoleOpenApiHelperTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.camel.tooling.model;
+
+import java.util.List;
+
+import org.apache.camel.tooling.model.DevConsoleModel.DevConsoleOptionModel;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The unit test class for {@link DevConsoleOpenApiHelper}.
+ */
+class DevConsoleOpenApiHelperTest {
+
+ private static DevConsoleOptionModel option(String name, String type, boolean required) {
+ DevConsoleOptionModel option = new DevConsoleOptionModel();
+ option.setName(name);
+ option.setType(type);
+ option.setRequired(required);
+ option.setDescription("The " + name);
+ return option;
+ }
+
+ @Test
+ void readOnlyConsoleWithOptionsBuildsGetWithParameters() {
+ DevConsoleModel model = new DevConsoleModel();
+ model.setName("route");
+ model.setTitle("Route");
+ model.setDescription("Route information");
+ model.setReadOnly(true);
+ model.addOption(option("filter", "string", false));
+ model.addOption(option("limit", "integer", false));
+
+ JsonObject pathItem = DevConsoleOpenApiHelper.buildPathItem(model);
+
+ assertNull(pathItem.get("post"));
+ JsonObject get = pathItem.getJsonObject("get");
+ assertNotNull(get);
+ assertEquals("route", get.getString("operationId"));
+ assertNull(get.get("requestBody"));
+ var parameters = get.getCollection("parameters");
+ assertEquals(2, parameters.size());
+ }
+
+ @Test
+ void mutatingConsoleWithOptionsBuildsPostWithRequestBody() {
+ DevConsoleModel model = new DevConsoleModel();
+ model.setName("route");
+ model.setTitle("Route");
+ model.setDescription("Route information");
+ model.setReadOnly(false);
+ model.addOption(option("action", "string", true));
+
+ JsonObject pathItem = DevConsoleOpenApiHelper.buildPathItem(model);
+
+ assertNull(pathItem.get("get"));
+ JsonObject post = pathItem.getJsonObject("post");
+ assertNotNull(post);
+ assertNull(post.get("parameters"));
+ JsonObject requestBody = post.getJsonObject("requestBody");
+ assertNotNull(requestBody);
+ JsonObject schema = requestBody.getJsonObject("content").getJsonObject("application/json").getJsonObject("schema");
+ assertTrue(schema.getCollection("required").contains("action"));
+ }
+
+ @Test
+ void consoleWithoutOptionsHasNeitherParametersNorRequestBody() {
+ DevConsoleModel model = new DevConsoleModel();
+ model.setName("context");
+ model.setTitle("CamelContext");
+ model.setDescription("Overall information about the CamelContext");
+ model.setReadOnly(true);
+
+ JsonObject pathItem = DevConsoleOpenApiHelper.buildPathItem(model);
+
+ JsonObject get = pathItem.getJsonObject("get");
+ assertNotNull(get);
+ assertNull(get.get("parameters"));
+ assertNull(get.get("requestBody"));
+ }
+
+ @Test
+ void openApiDocumentContainsOnePathPerConsoleSortedByName() {
+ DevConsoleModel context = new DevConsoleModel();
+ context.setName("context");
+ context.setTitle("CamelContext");
+ context.setDescription("Overall information about the CamelContext");
+ context.setReadOnly(true);
+
+ DevConsoleModel route = new DevConsoleModel();
+ route.setName("route");
+ route.setTitle("Route");
+ route.setDescription("Route information");
+ route.setReadOnly(false);
+
+ JsonObject doc = DevConsoleOpenApiHelper.buildOpenApiDocument(List.of(route, context), "4.23.0-SNAPSHOT");
+
+ assertEquals("3.0.3", doc.getString("openapi"));
+ assertEquals("4.23.0-SNAPSHOT", doc.getJsonObject("info").getString("version"));
+ JsonObject paths = doc.getJsonObject("paths");
+ assertNotNull(paths.getJsonObject("/q/dev/context").getJsonObject("get"));
+ assertNotNull(paths.getJsonObject("/q/dev/route").getJsonObject("post"));
+ assertFalse(paths.containsKey("/q/dev/missing"));
+ }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCatalogMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCatalogMojo.java
index 7ce21e3fa9c2..09c32609049b 100644
--- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCatalogMojo.java
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PrepareCatalogMojo.java
@@ -48,6 +48,7 @@ import org.apache.camel.tooling.model.BaseOptionModel;
import org.apache.camel.tooling.model.ComponentModel;
import org.apache.camel.tooling.model.DataFormatModel;
import org.apache.camel.tooling.model.DevConsoleModel;
+import org.apache.camel.tooling.model.DevConsoleOpenApiHelper;
import org.apache.camel.tooling.model.EipModel;
import org.apache.camel.tooling.model.JsonMapper;
import org.apache.camel.tooling.model.LanguageModel;
@@ -59,6 +60,8 @@ import org.apache.camel.tooling.model.TransformerModel;
import org.apache.camel.tooling.util.FileUtil;
import org.apache.camel.tooling.util.PackageHelper;
import org.apache.camel.tooling.util.Strings;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
@@ -899,6 +902,15 @@ public class PrepareCatalogMojo extends AbstractMojo {
= jsonFiles.stream().map(PrepareCatalogMojo::asComponentName).collect(Collectors.toCollection(TreeSet::new));
FileUtil.updateFile(all, String.join("\n", consoleNames) + "\n");
+ // build and write a single consolidated OpenAPI document describing every dev console
+ List<DevConsoleModel> models = jsonFiles.stream()
+ .map(p -> (DevConsoleModel) allModels.get(p))
+ .collect(Collectors.toList());
+ JsonObject openApi = DevConsoleOpenApiHelper.buildOpenApiDocument(models, project.getVersion());
+ Path openApiFile = consolesOutDir.resolve("../dev-consoles-openapi.json");
+ FileUtil.updateFile(openApiFile, Jsoner.prettyPrint(openApi.toJson()) + "\n");
+ getLog().info("Generated dev-consoles-openapi.json containing " + models.size() + " Camel dev console paths");
+
printConsolesReport(jsonFiles, duplicateJsonFiles);
return consoleNames;