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 78cc0bd0daa6 CAMEL-24564: camel-tooling-model - Generate a static
OpenAPI spec for all dev consoles and publish it in the camel-catalog (#25885)
78cc0bd0daa6 is described below
commit 78cc0bd0daa6c523c4435e819e952e6577e52b5e
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Aug 29 17:12:57 2026 +0200
CAMEL-24564: camel-tooling-model - Generate a static OpenAPI spec for all
dev consoles and publish it in the camel-catalog (#25885)
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.
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Sonnet 5 <[email protected]>
---
.../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;
