diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cron.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cron.json index f0a2ca0b9f63d..75c3a691f33d4 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cron.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cron.json @@ -28,6 +28,9 @@ "autowiredEnabled": { "index": 1, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." }, "cronService": { "index": 2, "kind": "property", "displayName": "Cron Service", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of the CamelCronService to use when multiple implementations are provided" } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "null" ], "description": "No body: the message only starts the route" } + }, "properties": { "name": { "index": 0, "kind": "path", "displayName": "Name", "group": "consumer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.cron.api.CamelCronConfiguration", "configurationField": "configuration", "description": "The name of the cron trigger" }, "schedule": { "index": 1, "kind": "parameter", "displayName": "Schedule", "group": "consumer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.cron.api.CamelCronConfiguration", "configurationField": "configuration", "description": "A cron expression that will be used to generate events" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/direct.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/direct.json index 25bd03ca6b52c..b79f963ec03b3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/direct.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/direct.json @@ -31,6 +31,11 @@ "timeout": { "index": 3, "kind": "property", "displayName": "Timeout", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "description": "The timeout value to use if block is enabled." }, "autowiredEnabled": { "index": 4, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "unchanged" ], "description": "The body the caller sent" }, + "producerIn": { "carriers": [ "any" ] }, + "producerOut": { "carriers": [ "unknown" ], "description": "The body the route it calls ends with" } + }, "properties": { "name": { "index": 0, "kind": "path", "displayName": "Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of direct endpoint" }, "bridgeErrorHandler": { "index": 1, "kind": "parameter", "displayName": "Bridge Error Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/file.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/file.json index 2d400e50bff03..5458c5f580788 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/file.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/file.json @@ -49,6 +49,11 @@ "CamelFileInitialOffset": { "index": 15, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "A long value containing the initial offset.", "constantName": "org.apache.camel.component.file.FileConstants#INITIAL_OFFSET" }, "CamelFileChecksum": { "index": 16, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The checksum of the file written, when checksumAlgorithm is in use", "constantName": "org.apache.camel.component.file.FileConstants#FILE_CHECKSUM" } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "file" ], "description": "The file as read: a GenericFile (a java.io.File with preMove), which converts to text, bytes or a stream" }, + "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], "byOption": [ { "option": "allowNullBody", "value": "true", "carriers": [ "null", "text", "bytes", "stream", "file" ] } ], "description": "What converts to an InputStream: text, bytes, a stream or a file. A Map or a List fails: marshal it first" }, + "producerOut": { "carriers": [ "unchanged" ] } + }, "properties": { "directoryName": { "index": 0, "kind": "path", "displayName": "Directory Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.io.File", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The starting directory" }, "charset": { "index": 1, "kind": "parameter", "displayName": "Charset", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "This option is used to specify the encoding of the file. You can use this on the consumer, to specify the encodings of the files, which allow Camel to know the charset it should load the file content in case the file content is being accessed. Likewise when writing a file, you can use this option to specify which charset to write the file as well. Do mind that when writing the file Camel may have to read the message content into memory to be able to convert the data into the configured charset, so do not use this if you have big messages." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json index f6b9d06e87f85..7c35d5b29b3e3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json @@ -95,6 +95,10 @@ "CamelHttpCharacterEncoding": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The character encoding.", "constantName": "org.apache.camel.component.http.HttpConstants#HTTP_CHARACTER_ENCODING" }, "Content-Type": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "important": true, "description": "The HTTP content type. Is set on both the IN and OUT message to provide a content type, such as text\/html.", "constantName": "org.apache.camel.component.http.HttpConstants#CONTENT_TYPE" } }, + "bodyTypes": { + "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], "description": "The request body: text, bytes, a stream or a file. A Map or a List fails: marshal it first. Without httpMethod, no body sends a GET and a body a POST" }, + "producerOut": { "carriers": [ "bytes", "stream", "null" ], "description": "The response body: a byte array when its Content-Length is at most responsePayloadStreamingThreshold (8192), otherwise the stream cache (the stream itself with disableStreamCache); null for a response without a body" } + }, "properties": { "httpUri": { "index": 0, "kind": "path", "displayName": "Http Uri", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.net.URI", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The url of the HTTP endpoint to call." }, "disableStreamCache": { "index": 1, "kind": "parameter", "displayName": "Disable Stream Cache", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Determines whether or not the raw input stream is cached or not. The Camel consumer (camel-servlet, camel-jetty etc.) will by default cache the input stream to support reading it multiple times to ensure it Camel can retrieve all data from the stream. However you can set this option to true when you for example need to access the raw stream, such as streaming it directly to a file or other persistent store. DefaultHttpBinding will copy the request input stream into a stream cache and put it into message body if this option is false to support reading the stream multiple times. If you use Servlet to bridge\/proxy an endpoint then consider enabling this option to improve performance, in case you do not need to read the message payload multiple times. The producer (camel-http) will by default cache the response body stream. If setting this option to true, then the producers will not cache the response body stream but use the response stream as-is (the stream can only be read once) as the message body." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json index d17bb0c1e3239..d3f802d744179 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json @@ -95,6 +95,10 @@ "CamelHttpCharacterEncoding": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The character encoding.", "constantName": "org.apache.camel.component.http.HttpConstants#HTTP_CHARACTER_ENCODING" }, "Content-Type": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "important": true, "description": "The HTTP content type. Is set on both the IN and OUT message to provide a content type, such as text\/html.", "constantName": "org.apache.camel.component.http.HttpConstants#CONTENT_TYPE" } }, + "bodyTypes": { + "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], "description": "The request body: text, bytes, a stream or a file. A Map or a List fails: marshal it first. Without httpMethod, no body sends a GET and a body a POST" }, + "producerOut": { "carriers": [ "bytes", "stream", "null" ], "description": "The response body: a byte array when its Content-Length is at most responsePayloadStreamingThreshold (8192), otherwise the stream cache (the stream itself with disableStreamCache); null for a response without a body" } + }, "properties": { "httpUri": { "index": 0, "kind": "path", "displayName": "Http Uri", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.net.URI", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The url of the HTTP endpoint to call." }, "disableStreamCache": { "index": 1, "kind": "parameter", "displayName": "Disable Stream Cache", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Determines whether or not the raw input stream is cached or not. The Camel consumer (camel-servlet, camel-jetty etc.) will by default cache the input stream to support reading it multiple times to ensure it Camel can retrieve all data from the stream. However you can set this option to true when you for example need to access the raw stream, such as streaming it directly to a file or other persistent store. DefaultHttpBinding will copy the request input stream into a stream cache and put it into message body if this option is false to support reading the stream multiple times. If you use Servlet to bridge\/proxy an endpoint then consider enabling this option to improve performance, in case you do not need to read the message payload multiple times. The producer (camel-http) will by default cache the response body stream. If setting this option to true, then the producers will not cache the response body stream but use the response stream as-is (the stream can only be read once) as the message body." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kamelet.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kamelet.json index 282770899b57b..d8ac7c103040e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kamelet.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/kamelet.json @@ -36,6 +36,11 @@ "noErrorHandler": { "index": 9, "kind": "property", "displayName": "No Error Handler", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether kamelets should use error handling or not. By default, the Kamelet uses the same error handler as from the calling route. This means that if the calling route has error handling that performs retries, or routing to a dead letter channel, then the kamelet route will use this also. This can be turned off by setting this option to true. If off then the kamelet route is not using error handling, and any exception thrown will for source kamelets be logged by the consumer, and the sink\/action kamelets will fail processing." }, "routeTemplateLoaderListener": { "index": 10, "kind": "property", "displayName": "Route Template Loader Listener", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.RouteTemplateLoaderListener", "deprecated": false, "autowired": true, "secret": false, "description": "To plugin a custom listener for when the Kamelet component is loading Kamelets from external resources." } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "unknown" ], "description": "The body the Kamelet sends, as its data types or its template say. In the template of a Kamelet, kamelet:source gives the body the caller sent" }, + "producerIn": { "carriers": [ "any" ] }, + "producerOut": { "carriers": [ "unknown" ], "description": "The body the template of the Kamelet ends with" } + }, "properties": { "templateId": { "index": 0, "kind": "path", "displayName": "Template Id", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Route Template ID" }, "routeId": { "index": 1, "kind": "path", "displayName": "Route Id", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Route ID. Default value notice: The ID will be auto-generated if not provided" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-openapi.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-openapi.json index d649162cff1c1..22417ab779dfc 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-openapi.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-openapi.json @@ -46,6 +46,10 @@ "sslContextParameters": { "index": 19, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "Customize TLS parameters used by the component. If not set defaults to the TLS parameters set in the Camel context" }, "useGlobalSslContextParameters": { "index": 20, "kind": "property", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enable usage of global SSL context parameters." } }, + "bodyTypes": { + "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], "dependsOn": [ "componentName" ], "description": "The request body of the operation, which the http component sends: text, bytes, a stream or a file. A Map or a List fails: marshal it first" }, + "producerOut": { "carriers": [ "bytes", "stream", "null" ], "dependsOn": [ "componentName" ], "description": "The response body, as the http component gives it: a byte array or the stream cache; null for a response without a body" } + }, "properties": { "specificationUri": { "index": 0, "kind": "path", "displayName": "Specification Uri", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "openapi.json", "description": "Path to the OpenApi specification file. The scheme, host base path are taken from this specification, but these can be overridden with properties on the component or endpoint level. If not given the component tries to load openapi.json resource from the classpath. Note that the host defined on the component and endpoint of this Component should contain the scheme, hostname and optionally the port in the URI syntax (i.e. http:\/\/api.example.com:8080). Overrides component configuration. The OpenApi specification can be loaded from different sources by prefixing with file: classpath: http: https:. Support for https is limited to using the JDK installed UrlHandler, and as such it can be cumbersome to setup TLS\/SSL certificates for https (such as setting a number of javax.net.ssl JVM system properties). How to do that consult the JDK documentation for UrlHandler. Default value notice: By default loads openapi.json file" }, "operationId": { "index": 1, "kind": "path", "displayName": "Operation Id", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "ID of the operation from the OpenApi specification. This is required when using producer" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/seda.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/seda.json index d3e1df34277b5..268b123f87582 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/seda.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/seda.json @@ -36,6 +36,11 @@ "defaultQueueFactory": { "index": 8, "kind": "property", "displayName": "Default Queue Factory", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.seda.BlockingQueueFactory", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the default queue factory." }, "queueSize": { "index": 9, "kind": "property", "displayName": "Queue Size", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "description": "Sets the default maximum capacity of the SEDA queue (i.e., the number of messages it can hold)." } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "unchanged" ], "description": "The body the producer sent" }, + "producerIn": { "carriers": [ "any" ] }, + "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option": "waitForTaskToComplete", "value": "Never", "carriers": [ "unchanged" ] } ], "description": "Unchanged for a message that expects no reply (InOnly). A message that expects one (InOut), or waitForTaskToComplete=Always, waits for the body the seda route ends with" } + }, "properties": { "name": { "index": 0, "kind": "path", "displayName": "Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of queue" }, "size": { "index": 1, "kind": "parameter", "displayName": "Size", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "description": "The maximum capacity of the SEDA queue (i.e., the number of messages it can hold). Will by default use the queueSize set on the SEDA component." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json index c537163715c87..371e35003ff24 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json @@ -48,6 +48,11 @@ "CamelSqlParameters": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Iterator", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The SQL parameters when using the option useMessageBodyForSql", "constantName": "org.apache.camel.component.sql.SqlConstants#SQL_PARAMETERS" }, "CamelSqlDataSource": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "javax.sql.DataSource", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To use a specific DataSource to operate on another database than the pre configured DataSource on the component", "constantName": "org.apache.camel.component.sql.SqlConstants#SQL_DATA_SOURCE" } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "map" ], "javaType": "java.util.Map", "dependsOn": [ "outputClass", "outputHeader" ], "byOption": [ { "option": "useIterator", "value": "false", "carriers": [ "list" ] }, { "option": "outputType", "value": "SelectOne", "carriers": [ "map", "value" ] }, { "option": "outputType", "value": "StreamList", "carriers": [ "iterator" ] } ], "description": "A row of the query: a Map of the column names and their values" }, + "producerIn": { "carriers": [ "any" ], "description": "The parameters of the query: a Map for the named ones, a List or an array for the # ones; the query itself with useMessageBodyForSql" }, + "producerOut": { "carriers": [ "list", "unchanged" ], "dependsOn": [ "outputClass", "outputHeader", "noop" ], "byOption": [ { "option": "outputType", "value": "SelectOne", "carriers": [ "map", "value", "null" ] }, { "option": "outputType", "value": "StreamList", "carriers": [ "iterator" ] } ], "description": "The rows of a select: a List of Maps of the column names and their values. A query that is not a select leaves the body unchanged, with the count in the CamelSqlUpdateCount header" } + }, "properties": { "query": { "index": 0, "kind": "path", "displayName": "Query", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "supportFileReference": true, "largeInput": true, "inputLanguage": "sql", "description": "Sets the SQL query to perform. You can externalize the query by using file: or classpath: as prefix and specify the location of the file." }, "allowNamedParameters": { "index": 1, "kind": "parameter", "displayName": "Allow Named Parameters", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to allow using named parameters in the queries." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/timer.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/timer.json index fef8e3d02c437..338cb6f43820c 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/timer.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/timer.json @@ -33,6 +33,9 @@ "CamelTimerFiredTime": { "index": 0, "kind": "header", "displayName": "", "group": "consumer", "label": "", "required": false, "javaType": "Date", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The fired time", "constantName": "org.apache.camel.component.timer.TimerConstants#HEADER_FIRED_TIME" }, "CamelMessageTimestamp": { "index": 1, "kind": "header", "displayName": "", "group": "consumer", "label": "", "required": false, "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The timestamp of the message", "constantName": "org.apache.camel.component.timer.TimerConstants#HEADER_MESSAGE_TIMESTAMP" } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "null" ], "description": "No body: the message only starts the route" } + }, "properties": { "timerName": { "index": 0, "kind": "path", "displayName": "Timer Name", "group": "consumer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the timer" }, "delay": { "index": 1, "kind": "parameter", "displayName": "Delay", "group": "consumer", "label": "", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "1000", "description": "The number of milliseconds to wait before the first event is generated. Should not be used in conjunction with the time option. The default value is 1000." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/xslt.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/xslt.json index 0b2ffa8645429..7c6a1d43795b0 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/xslt.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/xslt.json @@ -39,6 +39,10 @@ "CamelXsltStylesheet": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The template to use instead of the endpoint configured.", "constantName": "org.apache.camel.component.xslt.XsltConstants#XSLT_STYLESHEET" }, "CamelXsltFileName": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The XSLT file name", "constantName": "org.apache.camel.component.xslt.XsltConstants#XSLT_FILE_NAME" } }, + "bodyTypes": { + "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], "format": "xml", "dependsOn": [ "source" ], "byOption": [ { "option": "failOnNullBody", "value": "false", "carriers": [ "null", "text", "bytes", "stream", "file", "pojo" ] } ], "description": "The XML to transform: text, bytes, a stream, a file, a DOM or another javax.xml.transform.Source. A Map, a List or no body fails" }, + "producerOut": { "carriers": [ "text" ], "javaType": "java.lang.String", "dependsOn": [ "resultHandlerFactory" ], "byOption": [ { "option": "output", "value": "bytes", "carriers": [ "bytes" ], "javaType": "byte[]" }, { "option": "output", "value": "DOM", "carriers": [ "pojo" ], "javaType": "org.w3c.dom.Document" }, { "option": "output", "value": "file", "carriers": [ "file" ], "javaType": "java.io.File" } ], "description": "The result of the transformation" } + }, "properties": { "resourceUri": { "index": 0, "kind": "path", "displayName": "Resource Uri", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Path to the template. The following is supported by the default URIResolver. You can prefix with: classpath, file, http, ref, or bean. classpath, file and http loads the resource using these protocols (classpath is default). ref will lookup the resource in the registry. bean will call a method on a bean to be used as the resource. For bean you can specify the method name after dot, eg bean:myBean.myMethod" }, "allowTemplateFromHeader": { "index": 1, "kind": "parameter", "displayName": "Allow Template From Header", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to allow to use resource template from header or not (default false). Enabling this allows to specify dynamic templates via message header. However this can be seen as a potential security vulnerability if the header is coming from a malicious user, so use this with care." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/jackson.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/jackson.json index 60f4d41e2401d..36de62e80cf39 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/jackson.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dataformats/jackson.json @@ -15,6 +15,12 @@ "modelName": "json", "modelJavaType": "org.apache.camel.model.dataformat.JsonDataFormat" }, + "bodyTypes": { + "marshalIn": { "carriers": [ "any" ], "description": "A Map, a List or a POJO to write as JSON. Text, bytes or a stream that is JSON already is written as it is" }, + "marshalOut": { "carriers": [ "stream" ], "format": "json", "description": "The JSON in the stream cache, or a byte array when stream caching is off" }, + "unmarshalIn": { "carriers": [ "text", "bytes", "stream", "file" ], "format": "json" }, + "unmarshalOut": { "carriers": [ "map", "list" ], "dependsOn": [ "unmarshalType", "collectionType", "allowUnmarshallType" ], "byOption": [ { "option": "useList", "value": "true", "carriers": [ "list" ] } ], "description": "A Map (LinkedHashMap) for a JSON object, a List (ArrayList) for an array; an object of the class unmarshalType names" } + }, "properties": { "id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" }, "objectMapper": { "index": 1, "kind": "attribute", "displayName": "Object Mapper", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Lookup and use the existing ObjectMapper with the given id when using Jackson." }, diff --git a/components/camel-cron/src/generated/resources/META-INF/org/apache/camel/component/cron/cron.json b/components/camel-cron/src/generated/resources/META-INF/org/apache/camel/component/cron/cron.json index f0a2ca0b9f63d..75c3a691f33d4 100644 --- a/components/camel-cron/src/generated/resources/META-INF/org/apache/camel/component/cron/cron.json +++ b/components/camel-cron/src/generated/resources/META-INF/org/apache/camel/component/cron/cron.json @@ -28,6 +28,9 @@ "autowiredEnabled": { "index": 1, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." }, "cronService": { "index": 2, "kind": "property", "displayName": "Cron Service", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of the CamelCronService to use when multiple implementations are provided" } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "null" ], "description": "No body: the message only starts the route" } + }, "properties": { "name": { "index": 0, "kind": "path", "displayName": "Name", "group": "consumer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.cron.api.CamelCronConfiguration", "configurationField": "configuration", "description": "The name of the cron trigger" }, "schedule": { "index": 1, "kind": "parameter", "displayName": "Schedule", "group": "consumer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.cron.api.CamelCronConfiguration", "configurationField": "configuration", "description": "A cron expression that will be used to generate events" }, diff --git a/components/camel-cron/src/main/java/org/apache/camel/component/cron/CronEndpoint.java b/components/camel-cron/src/main/java/org/apache/camel/component/cron/CronEndpoint.java index b67ea6348f813..716fab485903a 100644 --- a/components/camel-cron/src/main/java/org/apache/camel/component/cron/CronEndpoint.java +++ b/components/camel-cron/src/main/java/org/apache/camel/component/cron/CronEndpoint.java @@ -23,6 +23,9 @@ import org.apache.camel.Processor; import org.apache.camel.Producer; import org.apache.camel.component.cron.api.CamelCronConfiguration; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; import org.apache.camel.support.DefaultEndpoint; @@ -34,6 +37,7 @@ */ @UriEndpoint(firstVersion = "3.1.0", scheme = "cron", title = "Cron", syntax = "cron:name", consumerOnly = true, remote = false, category = { Category.SCHEDULING }) +@BodyTypes(consumerOut = @BodyType(value = Carrier.NULL, description = "No body: the message only starts the route")) public class CronEndpoint extends DefaultEndpoint implements DelegateEndpoint { private Endpoint delegate; diff --git a/components/camel-direct/src/generated/resources/META-INF/org/apache/camel/component/direct/direct.json b/components/camel-direct/src/generated/resources/META-INF/org/apache/camel/component/direct/direct.json index 25bd03ca6b52c..b79f963ec03b3 100644 --- a/components/camel-direct/src/generated/resources/META-INF/org/apache/camel/component/direct/direct.json +++ b/components/camel-direct/src/generated/resources/META-INF/org/apache/camel/component/direct/direct.json @@ -31,6 +31,11 @@ "timeout": { "index": 3, "kind": "property", "displayName": "Timeout", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 30000, "description": "The timeout value to use if block is enabled." }, "autowiredEnabled": { "index": 4, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc." } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "unchanged" ], "description": "The body the caller sent" }, + "producerIn": { "carriers": [ "any" ] }, + "producerOut": { "carriers": [ "unknown" ], "description": "The body the route it calls ends with" } + }, "properties": { "name": { "index": 0, "kind": "path", "displayName": "Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of direct endpoint" }, "bridgeErrorHandler": { "index": 1, "kind": "parameter", "displayName": "Bridge Error Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, diff --git a/components/camel-direct/src/main/java/org/apache/camel/component/direct/DirectEndpoint.java b/components/camel-direct/src/main/java/org/apache/camel/component/direct/DirectEndpoint.java index 1452087ad592a..d40853506ee56 100644 --- a/components/camel-direct/src/main/java/org/apache/camel/component/direct/DirectEndpoint.java +++ b/components/camel-direct/src/main/java/org/apache/camel/component/direct/DirectEndpoint.java @@ -20,6 +20,9 @@ import org.apache.camel.Consumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -34,6 +37,9 @@ */ @UriEndpoint(firstVersion = "1.0.0", scheme = "direct", title = "Direct", syntax = "direct:name", remote = false, internal = true, category = { Category.CORE, Category.MESSAGING }) +@BodyTypes(consumerOut = @BodyType(value = Carrier.UNCHANGED, description = "The body the caller sent"), + producerIn = @BodyType(Carrier.ANY), + producerOut = @BodyType(value = Carrier.UNKNOWN, description = "The body the route it calls ends with")) public class DirectEndpoint extends DefaultEndpoint { private final DirectComponent component; diff --git a/components/camel-file/src/generated/resources/META-INF/org/apache/camel/component/file/file.json b/components/camel-file/src/generated/resources/META-INF/org/apache/camel/component/file/file.json index 2d400e50bff03..5458c5f580788 100644 --- a/components/camel-file/src/generated/resources/META-INF/org/apache/camel/component/file/file.json +++ b/components/camel-file/src/generated/resources/META-INF/org/apache/camel/component/file/file.json @@ -49,6 +49,11 @@ "CamelFileInitialOffset": { "index": 15, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "A long value containing the initial offset.", "constantName": "org.apache.camel.component.file.FileConstants#INITIAL_OFFSET" }, "CamelFileChecksum": { "index": 16, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The checksum of the file written, when checksumAlgorithm is in use", "constantName": "org.apache.camel.component.file.FileConstants#FILE_CHECKSUM" } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "file" ], "description": "The file as read: a GenericFile (a java.io.File with preMove), which converts to text, bytes or a stream" }, + "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], "byOption": [ { "option": "allowNullBody", "value": "true", "carriers": [ "null", "text", "bytes", "stream", "file" ] } ], "description": "What converts to an InputStream: text, bytes, a stream or a file. A Map or a List fails: marshal it first" }, + "producerOut": { "carriers": [ "unchanged" ] } + }, "properties": { "directoryName": { "index": 0, "kind": "path", "displayName": "Directory Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.io.File", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The starting directory" }, "charset": { "index": 1, "kind": "parameter", "displayName": "Charset", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "This option is used to specify the encoding of the file. You can use this on the consumer, to specify the encodings of the files, which allow Camel to know the charset it should load the file content in case the file content is being accessed. Likewise when writing a file, you can use this option to specify which charset to write the file as well. Do mind that when writing the file Camel may have to read the message content into memory to be able to convert the data into the configured charset, so do not use this if you have big messages." }, diff --git a/components/camel-file/src/main/java/org/apache/camel/component/file/FileEndpoint.java b/components/camel-file/src/main/java/org/apache/camel/component/file/FileEndpoint.java index 621e144f76cd6..97c70ff2f7dc4 100644 --- a/components/camel-file/src/main/java/org/apache/camel/component/file/FileEndpoint.java +++ b/components/camel-file/src/main/java/org/apache/camel/component/file/FileEndpoint.java @@ -31,6 +31,9 @@ import org.apache.camel.Processor; import org.apache.camel.component.file.strategy.FileMoveExistingStrategy; import org.apache.camel.component.file.strategy.FileProcessStrategyFactory; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -46,6 +49,17 @@ */ @UriEndpoint(firstVersion = "1.0.0", scheme = "file", title = "File", syntax = "file:directoryName", category = { Category.FILE, Category.CORE }, headersClass = FileConstants.class) +@BodyTypes(consumerOut = @BodyType(value = Carrier.FILE, + description = "The file as read: a GenericFile (a java.io.File with preMove), which" + + " converts to text, bytes or a stream"), + producerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE }, + byOption = @BodyType.When(option = "allowNullBody", value = "true", + carriers = { + Carrier.NULL, Carrier.TEXT, Carrier.BYTES, + Carrier.STREAM, Carrier.FILE }), + description = "What converts to an InputStream: text, bytes, a stream or a file. A Map or" + + " a List fails: marshal it first"), + producerOut = @BodyType(Carrier.UNCHANGED)) public class FileEndpoint extends GenericFileEndpoint { private static final Logger LOG = LoggerFactory.getLogger(FileEndpoint.class); diff --git a/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/http.json b/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/http.json index f6b9d06e87f85..7c35d5b29b3e3 100644 --- a/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/http.json +++ b/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/http.json @@ -95,6 +95,10 @@ "CamelHttpCharacterEncoding": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The character encoding.", "constantName": "org.apache.camel.component.http.HttpConstants#HTTP_CHARACTER_ENCODING" }, "Content-Type": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "important": true, "description": "The HTTP content type. Is set on both the IN and OUT message to provide a content type, such as text\/html.", "constantName": "org.apache.camel.component.http.HttpConstants#CONTENT_TYPE" } }, + "bodyTypes": { + "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], "description": "The request body: text, bytes, a stream or a file. A Map or a List fails: marshal it first. Without httpMethod, no body sends a GET and a body a POST" }, + "producerOut": { "carriers": [ "bytes", "stream", "null" ], "description": "The response body: a byte array when its Content-Length is at most responsePayloadStreamingThreshold (8192), otherwise the stream cache (the stream itself with disableStreamCache); null for a response without a body" } + }, "properties": { "httpUri": { "index": 0, "kind": "path", "displayName": "Http Uri", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.net.URI", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The url of the HTTP endpoint to call." }, "disableStreamCache": { "index": 1, "kind": "parameter", "displayName": "Disable Stream Cache", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Determines whether or not the raw input stream is cached or not. The Camel consumer (camel-servlet, camel-jetty etc.) will by default cache the input stream to support reading it multiple times to ensure it Camel can retrieve all data from the stream. However you can set this option to true when you for example need to access the raw stream, such as streaming it directly to a file or other persistent store. DefaultHttpBinding will copy the request input stream into a stream cache and put it into message body if this option is false to support reading the stream multiple times. If you use Servlet to bridge\/proxy an endpoint then consider enabling this option to improve performance, in case you do not need to read the message payload multiple times. The producer (camel-http) will by default cache the response body stream. If setting this option to true, then the producers will not cache the response body stream but use the response stream as-is (the stream can only be read once) as the message body." }, diff --git a/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/https.json b/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/https.json index d17bb0c1e3239..d3f802d744179 100644 --- a/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/https.json +++ b/components/camel-http/src/generated/resources/META-INF/org/apache/camel/component/http/https.json @@ -95,6 +95,10 @@ "CamelHttpCharacterEncoding": { "index": 12, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The character encoding.", "constantName": "org.apache.camel.component.http.HttpConstants#HTTP_CHARACTER_ENCODING" }, "Content-Type": { "index": 13, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "important": true, "description": "The HTTP content type. Is set on both the IN and OUT message to provide a content type, such as text\/html.", "constantName": "org.apache.camel.component.http.HttpConstants#CONTENT_TYPE" } }, + "bodyTypes": { + "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], "description": "The request body: text, bytes, a stream or a file. A Map or a List fails: marshal it first. Without httpMethod, no body sends a GET and a body a POST" }, + "producerOut": { "carriers": [ "bytes", "stream", "null" ], "description": "The response body: a byte array when its Content-Length is at most responsePayloadStreamingThreshold (8192), otherwise the stream cache (the stream itself with disableStreamCache); null for a response without a body" } + }, "properties": { "httpUri": { "index": 0, "kind": "path", "displayName": "Http Uri", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.net.URI", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The url of the HTTP endpoint to call." }, "disableStreamCache": { "index": 1, "kind": "parameter", "displayName": "Disable Stream Cache", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Determines whether or not the raw input stream is cached or not. The Camel consumer (camel-servlet, camel-jetty etc.) will by default cache the input stream to support reading it multiple times to ensure it Camel can retrieve all data from the stream. However you can set this option to true when you for example need to access the raw stream, such as streaming it directly to a file or other persistent store. DefaultHttpBinding will copy the request input stream into a stream cache and put it into message body if this option is false to support reading the stream multiple times. If you use Servlet to bridge\/proxy an endpoint then consider enabling this option to improve performance, in case you do not need to read the message payload multiple times. The producer (camel-http) will by default cache the response body stream. If setting this option to true, then the producers will not cache the response body stream but use the response stream as-is (the stream can only be read once) as the message body." }, diff --git a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java index f7f3c664db22d..334bf81faaa1a 100644 --- a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java +++ b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java @@ -35,6 +35,9 @@ import org.apache.camel.api.management.ManagedResource; import org.apache.camel.http.base.cookie.CookieHandler; import org.apache.camel.http.common.HttpCommonEndpoint; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -74,6 +77,15 @@ "protocol=http" }) @ManagedResource(description = "Managed HttpEndpoint") +@BodyTypes(producerIn = @BodyType(value = { Carrier.NULL, Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE }, + description = "The request body: text, bytes, a stream or a file. A Map or a List fails:" + + " marshal it first. Without httpMethod, no body sends a GET and a body a" + + " POST"), + producerOut = @BodyType(value = { Carrier.BYTES, Carrier.STREAM, Carrier.NULL }, + description = "The response body: a byte array when its Content-Length is at most" + + " responsePayloadStreamingThreshold (8192), otherwise the stream cache" + + " (the stream itself with disableStreamCache); null for a response" + + " without a body")) public class HttpEndpoint extends HttpCommonEndpoint implements LineNumberAware { private static final Logger LOG = LoggerFactory.getLogger(HttpEndpoint.class); diff --git a/components/camel-jackson/src/generated/resources/META-INF/org/apache/camel/component/jackson/jackson.json b/components/camel-jackson/src/generated/resources/META-INF/org/apache/camel/component/jackson/jackson.json index 60f4d41e2401d..36de62e80cf39 100644 --- a/components/camel-jackson/src/generated/resources/META-INF/org/apache/camel/component/jackson/jackson.json +++ b/components/camel-jackson/src/generated/resources/META-INF/org/apache/camel/component/jackson/jackson.json @@ -15,6 +15,12 @@ "modelName": "json", "modelJavaType": "org.apache.camel.model.dataformat.JsonDataFormat" }, + "bodyTypes": { + "marshalIn": { "carriers": [ "any" ], "description": "A Map, a List or a POJO to write as JSON. Text, bytes or a stream that is JSON already is written as it is" }, + "marshalOut": { "carriers": [ "stream" ], "format": "json", "description": "The JSON in the stream cache, or a byte array when stream caching is off" }, + "unmarshalIn": { "carriers": [ "text", "bytes", "stream", "file" ], "format": "json" }, + "unmarshalOut": { "carriers": [ "map", "list" ], "dependsOn": [ "unmarshalType", "collectionType", "allowUnmarshallType" ], "byOption": [ { "option": "useList", "value": "true", "carriers": [ "list" ] } ], "description": "A Map (LinkedHashMap) for a JSON object, a List (ArrayList) for an array; an object of the class unmarshalType names" } + }, "properties": { "id": { "index": 0, "kind": "attribute", "displayName": "Id", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The id of this node" }, "objectMapper": { "index": 1, "kind": "attribute", "displayName": "Object Mapper", "group": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Lookup and use the existing ObjectMapper with the given id when using Jackson." }, diff --git a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java index e01f3254ec18b..4afc8fb1cbe30 100644 --- a/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java +++ b/components/camel-jackson/src/main/java/org/apache/camel/component/jackson/JacksonDataFormat.java @@ -23,6 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.camel.Exchange; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.annotations.Dataformat; import org.apache.camel.support.JsonPayloadHelper; @@ -34,6 +37,17 @@ */ @Dataformat("jackson") @Metadata(excludeProperties = "library,permissions,dateFormatPattern") +@BodyTypes(marshalIn = @BodyType(value = Carrier.ANY, + description = "A Map, a List or a POJO to write as JSON. Text, bytes or a stream that is" + + " JSON already is written as it is"), + marshalOut = @BodyType(value = Carrier.STREAM, format = "json", + description = "The JSON in the stream cache, or a byte array when stream caching is off"), + unmarshalIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE }, format = "json"), + unmarshalOut = @BodyType(value = { Carrier.MAP, Carrier.LIST }, + dependsOn = { "unmarshalType", "collectionType", "allowUnmarshallType" }, + byOption = @BodyType.When(option = "useList", value = "true", carriers = Carrier.LIST), + description = "A Map (LinkedHashMap) for a JSON object, a List (ArrayList) for an array;" + + " an object of the class unmarshalType names")) public class JacksonDataFormat extends AbstractJacksonDataFormat { private static final Logger LOG = LoggerFactory.getLogger(JacksonDataFormat.class); diff --git a/components/camel-kamelet/src/generated/resources/META-INF/org/apache/camel/component/kamelet/kamelet.json b/components/camel-kamelet/src/generated/resources/META-INF/org/apache/camel/component/kamelet/kamelet.json index 282770899b57b..d8ac7c103040e 100644 --- a/components/camel-kamelet/src/generated/resources/META-INF/org/apache/camel/component/kamelet/kamelet.json +++ b/components/camel-kamelet/src/generated/resources/META-INF/org/apache/camel/component/kamelet/kamelet.json @@ -36,6 +36,11 @@ "noErrorHandler": { "index": 9, "kind": "property", "displayName": "No Error Handler", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether kamelets should use error handling or not. By default, the Kamelet uses the same error handler as from the calling route. This means that if the calling route has error handling that performs retries, or routing to a dead letter channel, then the kamelet route will use this also. This can be turned off by setting this option to true. If off then the kamelet route is not using error handling, and any exception thrown will for source kamelets be logged by the consumer, and the sink\/action kamelets will fail processing." }, "routeTemplateLoaderListener": { "index": 10, "kind": "property", "displayName": "Route Template Loader Listener", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.RouteTemplateLoaderListener", "deprecated": false, "autowired": true, "secret": false, "description": "To plugin a custom listener for when the Kamelet component is loading Kamelets from external resources." } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "unknown" ], "description": "The body the Kamelet sends, as its data types or its template say. In the template of a Kamelet, kamelet:source gives the body the caller sent" }, + "producerIn": { "carriers": [ "any" ] }, + "producerOut": { "carriers": [ "unknown" ], "description": "The body the template of the Kamelet ends with" } + }, "properties": { "templateId": { "index": 0, "kind": "path", "displayName": "Template Id", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Route Template ID" }, "routeId": { "index": 1, "kind": "path", "displayName": "Route Id", "group": "advanced", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The Route ID. Default value notice: The ID will be auto-generated if not provided" }, diff --git a/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletEndpoint.java b/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletEndpoint.java index 943f6fe5c129c..879ad17ef9caa 100644 --- a/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletEndpoint.java +++ b/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletEndpoint.java @@ -26,6 +26,9 @@ import org.apache.camel.Consumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.PropertyConfigurer; import org.apache.camel.spi.UriEndpoint; @@ -37,6 +40,12 @@ @UriEndpoint(firstVersion = "3.8.0", scheme = "kamelet", syntax = "kamelet:templateId/routeId", title = "Kamelet", lenientProperties = true, category = Category.CORE) +@BodyTypes(consumerOut = @BodyType(value = Carrier.UNKNOWN, + description = "The body the Kamelet sends, as its data types or its template say. In" + + " the template of a Kamelet, kamelet:source gives the body the caller" + + " sent"), + producerIn = @BodyType(Carrier.ANY), + producerOut = @BodyType(value = Carrier.UNKNOWN, description = "The body the template of the Kamelet ends with")) public class KameletEndpoint extends DefaultEndpoint implements AfterPropertiesConfigured { private final String key; private final Map kameletProperties; diff --git a/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json b/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json index d649162cff1c1..22417ab779dfc 100644 --- a/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json +++ b/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json @@ -46,6 +46,10 @@ "sslContextParameters": { "index": 19, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "Customize TLS parameters used by the component. If not set defaults to the TLS parameters set in the Camel context" }, "useGlobalSslContextParameters": { "index": 20, "kind": "property", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Enable usage of global SSL context parameters." } }, + "bodyTypes": { + "producerIn": { "carriers": [ "null", "text", "bytes", "stream", "file" ], "dependsOn": [ "componentName" ], "description": "The request body of the operation, which the http component sends: text, bytes, a stream or a file. A Map or a List fails: marshal it first" }, + "producerOut": { "carriers": [ "bytes", "stream", "null" ], "dependsOn": [ "componentName" ], "description": "The response body, as the http component gives it: a byte array or the stream cache; null for a response without a body" } + }, "properties": { "specificationUri": { "index": 0, "kind": "path", "displayName": "Specification Uri", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "openapi.json", "description": "Path to the OpenApi specification file. The scheme, host base path are taken from this specification, but these can be overridden with properties on the component or endpoint level. If not given the component tries to load openapi.json resource from the classpath. Note that the host defined on the component and endpoint of this Component should contain the scheme, hostname and optionally the port in the URI syntax (i.e. http:\/\/api.example.com:8080). Overrides component configuration. The OpenApi specification can be loaded from different sources by prefixing with file: classpath: http: https:. Support for https is limited to using the JDK installed UrlHandler, and as such it can be cumbersome to setup TLS\/SSL certificates for https (such as setting a number of javax.net.ssl JVM system properties). How to do that consult the JDK documentation for UrlHandler. Default value notice: By default loads openapi.json file" }, "operationId": { "index": 1, "kind": "path", "displayName": "Operation Id", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "ID of the operation from the OpenApi specification. This is required when using producer" }, diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java index d4c39cf24d1c4..4f1c00d080713 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java @@ -62,6 +62,9 @@ import org.apache.camel.component.rest.openapi.validator.DefaultRequestValidator; import org.apache.camel.component.rest.openapi.validator.RequestValidator; import org.apache.camel.component.rest.openapi.validator.RestOpenApiOperation; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.CamelInternalProcessorAdvice; import org.apache.camel.spi.InternalProcessor; import org.apache.camel.spi.Metadata; @@ -92,6 +95,13 @@ */ @UriEndpoint(firstVersion = "3.1.0", scheme = "rest-openapi", title = "REST OpenApi", syntax = "rest-openapi:specificationUri#operationId", category = { Category.REST, Category.API }) +@BodyTypes(producerIn = @BodyType(value = { Carrier.NULL, Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE }, + dependsOn = "componentName", + description = "The request body of the operation, which the http component sends: text," + + " bytes, a stream or a file. A Map or a List fails: marshal it first"), + producerOut = @BodyType(value = { Carrier.BYTES, Carrier.STREAM, Carrier.NULL }, dependsOn = "componentName", + description = "The response body, as the http component gives it: a byte array or the" + + " stream cache; null for a response without a body")) public final class RestOpenApiEndpoint extends DefaultEndpoint { private static final Logger LOG = LoggerFactory.getLogger(RestOpenApiEndpoint.class); diff --git a/components/camel-seda/src/generated/resources/META-INF/org/apache/camel/component/seda/seda.json b/components/camel-seda/src/generated/resources/META-INF/org/apache/camel/component/seda/seda.json index d3e1df34277b5..268b123f87582 100644 --- a/components/camel-seda/src/generated/resources/META-INF/org/apache/camel/component/seda/seda.json +++ b/components/camel-seda/src/generated/resources/META-INF/org/apache/camel/component/seda/seda.json @@ -36,6 +36,11 @@ "defaultQueueFactory": { "index": 8, "kind": "property", "displayName": "Default Queue Factory", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.seda.BlockingQueueFactory", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the default queue factory." }, "queueSize": { "index": 9, "kind": "property", "displayName": "Queue Size", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "description": "Sets the default maximum capacity of the SEDA queue (i.e., the number of messages it can hold)." } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "unchanged" ], "description": "The body the producer sent" }, + "producerIn": { "carriers": [ "any" ] }, + "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option": "waitForTaskToComplete", "value": "Never", "carriers": [ "unchanged" ] } ], "description": "Unchanged for a message that expects no reply (InOnly). A message that expects one (InOut), or waitForTaskToComplete=Always, waits for the body the seda route ends with" } + }, "properties": { "name": { "index": 0, "kind": "path", "displayName": "Name", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Name of queue" }, "size": { "index": 1, "kind": "parameter", "displayName": "Size", "group": "common", "label": "", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "description": "The maximum capacity of the SEDA queue (i.e., the number of messages it can hold). Will by default use the queueSize set on the SEDA component." }, diff --git a/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java b/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java index 0da80bde3d089..973bd1404bbd1 100644 --- a/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java +++ b/components/camel-seda/src/main/java/org/apache/camel/component/seda/SedaEndpoint.java @@ -40,6 +40,9 @@ import org.apache.camel.api.management.ManagedAttribute; import org.apache.camel.api.management.ManagedOperation; import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.BrowsableEndpoint; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; @@ -59,6 +62,14 @@ @ManagedResource(description = "Managed SedaEndpoint") @UriEndpoint(firstVersion = "1.1.0", scheme = "seda", title = "SEDA", syntax = "seda:name", remote = false, internal = true, category = { Category.CORE, Category.MESSAGING }) +@BodyTypes(consumerOut = @BodyType(value = Carrier.UNCHANGED, description = "The body the producer sent"), + producerIn = @BodyType(Carrier.ANY), + producerOut = @BodyType(value = Carrier.UNKNOWN, + byOption = @BodyType.When(option = "waitForTaskToComplete", value = "Never", + carriers = Carrier.UNCHANGED), + description = "Unchanged for a message that expects no reply (InOnly). A message that" + + " expects one (InOut), or waitForTaskToComplete=Always, waits for the" + + " body the seda route ends with")) public class SedaEndpoint extends DefaultEndpoint implements AsyncEndpoint, BrowsableEndpoint, MultipleConsumersSupport { private static final Logger LOG = LoggerFactory.getLogger(SedaEndpoint.class); diff --git a/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json b/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json index c537163715c87..371e35003ff24 100644 --- a/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json +++ b/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json @@ -48,6 +48,11 @@ "CamelSqlParameters": { "index": 7, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Iterator", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The SQL parameters when using the option useMessageBodyForSql", "constantName": "org.apache.camel.component.sql.SqlConstants#SQL_PARAMETERS" }, "CamelSqlDataSource": { "index": 8, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "javax.sql.DataSource", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "To use a specific DataSource to operate on another database than the pre configured DataSource on the component", "constantName": "org.apache.camel.component.sql.SqlConstants#SQL_DATA_SOURCE" } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "map" ], "javaType": "java.util.Map", "dependsOn": [ "outputClass", "outputHeader" ], "byOption": [ { "option": "useIterator", "value": "false", "carriers": [ "list" ] }, { "option": "outputType", "value": "SelectOne", "carriers": [ "map", "value" ] }, { "option": "outputType", "value": "StreamList", "carriers": [ "iterator" ] } ], "description": "A row of the query: a Map of the column names and their values" }, + "producerIn": { "carriers": [ "any" ], "description": "The parameters of the query: a Map for the named ones, a List or an array for the # ones; the query itself with useMessageBodyForSql" }, + "producerOut": { "carriers": [ "list", "unchanged" ], "dependsOn": [ "outputClass", "outputHeader", "noop" ], "byOption": [ { "option": "outputType", "value": "SelectOne", "carriers": [ "map", "value", "null" ] }, { "option": "outputType", "value": "StreamList", "carriers": [ "iterator" ] } ], "description": "The rows of a select: a List of Maps of the column names and their values. A query that is not a select leaves the body unchanged, with the count in the CamelSqlUpdateCount header" } + }, "properties": { "query": { "index": 0, "kind": "path", "displayName": "Query", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "supportFileReference": true, "largeInput": true, "inputLanguage": "sql", "description": "Sets the SQL query to perform. You can externalize the query by using file: or classpath: as prefix and specify the location of the file." }, "allowNamedParameters": { "index": 1, "kind": "parameter", "displayName": "Allow Named Parameters", "group": "common", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to allow using named parameters in the queries." }, diff --git a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlEndpoint.java b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlEndpoint.java index fd6affe1e4d1a..5a7798e61770e 100644 --- a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlEndpoint.java +++ b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlEndpoint.java @@ -21,6 +21,9 @@ import org.apache.camel.Consumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -32,6 +35,28 @@ */ @UriEndpoint(firstVersion = "1.4.0", scheme = "sql", title = "SQL", syntax = "sql:query", category = { Category.DATABASE }, headersClass = SqlConstants.class) +@BodyTypes(consumerOut = @BodyType(value = Carrier.MAP, javaType = "java.util.Map", + dependsOn = { "outputClass", "outputHeader" }, + byOption = { + @BodyType.When(option = "useIterator", value = "false", carriers = Carrier.LIST), + @BodyType.When(option = "outputType", value = "SelectOne", + carriers = { Carrier.MAP, Carrier.VALUE }), + @BodyType.When(option = "outputType", value = "StreamList", + carriers = Carrier.ITERATOR) }, + description = "A row of the query: a Map of the column names and their values"), + producerIn = @BodyType(value = Carrier.ANY, + description = "The parameters of the query: a Map for the named ones, a List or an array" + + " for the # ones; the query itself with useMessageBodyForSql"), + producerOut = @BodyType(value = { Carrier.LIST, Carrier.UNCHANGED }, + dependsOn = { "outputClass", "outputHeader", "noop" }, + byOption = { + @BodyType.When(option = "outputType", value = "SelectOne", + carriers = { Carrier.MAP, Carrier.VALUE, Carrier.NULL }), + @BodyType.When(option = "outputType", value = "StreamList", + carriers = Carrier.ITERATOR) }, + description = "The rows of a select: a List of Maps of the column names and their values." + + " A query that is not a select leaves the body unchanged, with the count" + + " in the CamelSqlUpdateCount header")) public class SqlEndpoint extends DefaultSqlEndpoint { @UriPath(description = "Sets the SQL query to perform. You can externalize the query by using file: or classpath: as prefix and specify the location of the file.") diff --git a/components/camel-timer/src/generated/resources/META-INF/org/apache/camel/component/timer/timer.json b/components/camel-timer/src/generated/resources/META-INF/org/apache/camel/component/timer/timer.json index fef8e3d02c437..338cb6f43820c 100644 --- a/components/camel-timer/src/generated/resources/META-INF/org/apache/camel/component/timer/timer.json +++ b/components/camel-timer/src/generated/resources/META-INF/org/apache/camel/component/timer/timer.json @@ -33,6 +33,9 @@ "CamelTimerFiredTime": { "index": 0, "kind": "header", "displayName": "", "group": "consumer", "label": "", "required": false, "javaType": "Date", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The fired time", "constantName": "org.apache.camel.component.timer.TimerConstants#HEADER_FIRED_TIME" }, "CamelMessageTimestamp": { "index": 1, "kind": "header", "displayName": "", "group": "consumer", "label": "", "required": false, "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The timestamp of the message", "constantName": "org.apache.camel.component.timer.TimerConstants#HEADER_MESSAGE_TIMESTAMP" } }, + "bodyTypes": { + "consumerOut": { "carriers": [ "null" ], "description": "No body: the message only starts the route" } + }, "properties": { "timerName": { "index": 0, "kind": "path", "displayName": "Timer Name", "group": "consumer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the timer" }, "delay": { "index": 1, "kind": "parameter", "displayName": "Delay", "group": "consumer", "label": "", "required": false, "type": "duration", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "1000", "description": "The number of milliseconds to wait before the first event is generated. Should not be used in conjunction with the time option. The default value is 1000." }, diff --git a/components/camel-timer/src/main/java/org/apache/camel/component/timer/TimerEndpoint.java b/components/camel-timer/src/main/java/org/apache/camel/component/timer/TimerEndpoint.java index 943e98c3783b8..8fcb40f7acad9 100644 --- a/components/camel-timer/src/main/java/org/apache/camel/component/timer/TimerEndpoint.java +++ b/components/camel-timer/src/main/java/org/apache/camel/component/timer/TimerEndpoint.java @@ -29,6 +29,9 @@ import org.apache.camel.RuntimeCamelException; import org.apache.camel.api.management.ManagedAttribute; import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -44,6 +47,7 @@ @UriEndpoint(firstVersion = "1.0.0", scheme = "timer", title = "Timer", syntax = "timer:timerName", consumerOnly = true, remote = false, internal = true, category = { Category.CORE, Category.SCHEDULING }, headersClass = TimerConstants.class) +@BodyTypes(consumerOut = @BodyType(value = Carrier.NULL, description = "No body: the message only starts the route")) public class TimerEndpoint extends DefaultEndpoint implements MultipleConsumersSupport { @UriPath @Metadata(required = true) diff --git a/components/camel-xslt/src/generated/resources/META-INF/org/apache/camel/component/xslt/xslt.json b/components/camel-xslt/src/generated/resources/META-INF/org/apache/camel/component/xslt/xslt.json index 0b2ffa8645429..7c6a1d43795b0 100644 --- a/components/camel-xslt/src/generated/resources/META-INF/org/apache/camel/component/xslt/xslt.json +++ b/components/camel-xslt/src/generated/resources/META-INF/org/apache/camel/component/xslt/xslt.json @@ -39,6 +39,10 @@ "CamelXsltStylesheet": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The template to use instead of the endpoint configured.", "constantName": "org.apache.camel.component.xslt.XsltConstants#XSLT_STYLESHEET" }, "CamelXsltFileName": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The XSLT file name", "constantName": "org.apache.camel.component.xslt.XsltConstants#XSLT_FILE_NAME" } }, + "bodyTypes": { + "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], "format": "xml", "dependsOn": [ "source" ], "byOption": [ { "option": "failOnNullBody", "value": "false", "carriers": [ "null", "text", "bytes", "stream", "file", "pojo" ] } ], "description": "The XML to transform: text, bytes, a stream, a file, a DOM or another javax.xml.transform.Source. A Map, a List or no body fails" }, + "producerOut": { "carriers": [ "text" ], "javaType": "java.lang.String", "dependsOn": [ "resultHandlerFactory" ], "byOption": [ { "option": "output", "value": "bytes", "carriers": [ "bytes" ], "javaType": "byte[]" }, { "option": "output", "value": "DOM", "carriers": [ "pojo" ], "javaType": "org.w3c.dom.Document" }, { "option": "output", "value": "file", "carriers": [ "file" ], "javaType": "java.io.File" } ], "description": "The result of the transformation" } + }, "properties": { "resourceUri": { "index": 0, "kind": "path", "displayName": "Resource Uri", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Path to the template. The following is supported by the default URIResolver. You can prefix with: classpath, file, http, ref, or bean. classpath, file and http loads the resource using these protocols (classpath is default). ref will lookup the resource in the registry. bean will call a method on a bean to be used as the resource. For bean you can specify the method name after dot, eg bean:myBean.myMethod" }, "allowTemplateFromHeader": { "index": 1, "kind": "parameter", "displayName": "Allow Template From Header", "group": "producer", "label": "", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to allow to use resource template from header or not (default false). Enabling this allows to specify dynamic templates via message header. However this can be seen as a potential security vulnerability if the header is coming from a malicious user, so use this with care." }, diff --git a/components/camel-xslt/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java b/components/camel-xslt/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java index d7dd5b0ec1b8d..c02124dc45695 100644 --- a/components/camel-xslt/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java +++ b/components/camel-xslt/src/main/java/org/apache/camel/component/xslt/XsltEndpoint.java @@ -39,6 +39,9 @@ import org.apache.camel.api.management.ManagedAttribute; import org.apache.camel.api.management.ManagedOperation; import org.apache.camel.api.management.ManagedResource; +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.ClassResolver; import org.apache.camel.spi.Injector; import org.apache.camel.spi.Metadata; @@ -60,6 +63,23 @@ @ManagedResource(description = "Managed XsltEndpoint") @UriEndpoint(firstVersion = "1.3.0", scheme = "xslt", title = "XSLT", syntax = "xslt:resourceUri", producerOnly = true, remote = false, category = { Category.CORE, Category.TRANSFORMATION }, headersClass = XsltConstants.class) +@BodyTypes(producerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE, Carrier.POJO }, + format = "xml", dependsOn = "source", + byOption = @BodyType.When(option = "failOnNullBody", value = "false", + carriers = { + Carrier.NULL, Carrier.TEXT, Carrier.BYTES, + Carrier.STREAM, Carrier.FILE, Carrier.POJO }), + description = "The XML to transform: text, bytes, a stream, a file, a DOM or another" + + " javax.xml.transform.Source. A Map, a List or no body fails"), + producerOut = @BodyType(value = Carrier.TEXT, javaType = "java.lang.String", dependsOn = "resultHandlerFactory", + byOption = { + @BodyType.When(option = "output", value = "bytes", carriers = Carrier.BYTES, + javaType = "byte[]"), + @BodyType.When(option = "output", value = "DOM", carriers = Carrier.POJO, + javaType = "org.w3c.dom.Document"), + @BodyType.When(option = "output", value = "file", carriers = Carrier.FILE, + javaType = "java.io.File") }, + description = "The result of the transformation")) public class XsltEndpoint extends ProcessorEndpoint { private static final Logger LOG = LoggerFactory.getLogger(XsltEndpoint.class); diff --git a/core/camel-api/src/generated/java/org/apache/camel/spi/BodyType.java b/core/camel-api/src/generated/java/org/apache/camel/spi/BodyType.java new file mode 100644 index 0000000000000..2d2cc3b927240 --- /dev/null +++ b/core/camel-api/src/generated/java/org/apache/camel/spi/BodyType.java @@ -0,0 +1,153 @@ +/* + * 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.spi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * One side of {@link BodyTypes}: the body a step needs or leaves. + *

+ * The carriers are coarse on purpose: a carrier that is right is better than a precise Java type that is wrong. Give + * the {@link #javaType()} only when the body is always of that type. + * + * @since 4.24 + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({}) +public @interface BodyType { + + /** + * The carriers of the body: the kinds of Java object it is in. Several for a body that can be any of them. None + * when the side is not known. + */ + Carrier[] value() default {}; + + /** + * The Java type of the body, when it is always of this type, such as + * {@code org.apache.camel.component.file.GenericFile}. + */ + String javaType() default ""; + + /** + * The format of the payload, when it is always the same: json, xml, csv, yaml, text or binary. These are the names + * of the data types of Kamelets. + */ + String format() default ""; + + /** + * The options the body depends on. The carriers hold while these options keep their default values; with another + * value the body is not known, unless {@link #byOption()} says it. + */ + String[] dependsOn() default {}; + + /** + * The body for other values of an option, such as {@code output=bytes}. + */ + When[] byOption() default {}; + + /** + * What to know about the body, in a sentence for the documentation and the tools. + */ + String description() default ""; + + /** + * The body when an option has a value. + */ + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Target({}) + @interface When { + + /** + * The name of the option. + */ + String option(); + + /** + * The value of the option. + */ + String value(); + + /** + * The carriers of the body with this value. + */ + Carrier[] carriers(); + + /** + * The Java type of the body with this value, when it is always of this type. + */ + String javaType() default ""; + } + + /** + * The kinds of Java object a body is in. The runtime sorts the bodies it records into kinds as well (null, payload, + * data and collection): text, bytes, a stream, a file and a value are a payload, a map and a pojo are data, a list + * and an iterator are a collection. + */ + enum Carrier { + + /** No body. */ + NULL("null"), + /** Text: a String. */ + TEXT("text"), + /** A byte array. */ + BYTES("bytes"), + /** A stream: an InputStream, a Reader, or the stream cache of Camel. */ + STREAM("stream"), + /** A file: the GenericFile of the file consumers, a java.io.File or a Path. */ + FILE("file"), + /** A single value: a number, a boolean or an enum. */ + VALUE("value"), + /** A java.util.Map, such as a JSON object parsed or a row of a query. */ + MAP("map"), + /** A List, another Collection or an array, such as a JSON array parsed or the rows of a query. */ + LIST("list"), + /** + * An Iterator, which is read once, element by element, and has no size and no index: the rows of a query that + * streams them, or of a CSV file read lazily. + */ + ITERATOR("iterator"), + /** Another Java object, such as a DOM Document or a class a payload is unmarshalled into. */ + POJO("pojo"), + /** The body as it was before the step: for the body after a step only. */ + UNCHANGED("unchanged"), + /** Any body: for the bodies a step needs only. */ + ANY("any"), + /** + * The body depends on what the other system sends, or on a class the user configures: tooling must not guess + * it. + */ + UNKNOWN("unknown"); + + private final String value; + + Carrier(String value) { + this.value = value; + } + + /** + * The name of the carrier, as the catalog has it. + */ + public String getValue() { + return value; + } + } +} diff --git a/core/camel-api/src/generated/java/org/apache/camel/spi/BodyTypes.java b/core/camel-api/src/generated/java/org/apache/camel/spi/BodyTypes.java new file mode 100644 index 0000000000000..485ac6c17c7d8 --- /dev/null +++ b/core/camel-api/src/generated/java/org/apache/camel/spi/BodyTypes.java @@ -0,0 +1,105 @@ +/* + * 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.spi; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The message body a component, data format or language works with: the body it needs, and the body it leaves after it. + * The build carries it into the catalog, so tooling can say what the body is after a step without running the route: + * the validators, the catalog tools and the documentation. + *

+ * Put it on the endpoint class (the class with {@link UriEndpoint}), the data format class or the language class. A + * subclass does not have the body types of its superclass, as it may change the body: it declares its own. + *

+ * Each side that is given says what the body is with the default options. A side that is not given is not known, and + * tooling must not guess it. A side whose body depends on what the other system sends, or on a class the user + * configures, says {@link BodyType.Carrier#UNKNOWN}. + *

+ * For example the file component: the consumer gives the file it read, the producer writes text, bytes, a stream or a + * file, and leaves the body as it was: + * + *

+ * @BodyTypes(consumerOut = @BodyType(value = Carrier.FILE, description = "The file as read"),
+ *            producerIn = @BodyType({ Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE }),
+ *            producerOut = @BodyType(Carrier.UNCHANGED))
+ * 
+ * + * @since 4.24 + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ ElementType.TYPE }) +public @interface BodyTypes { + + /** + * The body of the message the consumer creates. + */ + BodyType consumerOut() default @BodyType; + + /** + * The bodies a consumer that answers the caller can reply with: the body the route ends with, such as the response + * of an HTTP server. + */ + BodyType consumerReply() default @BodyType; + + /** + * The bodies the producer can send or write. + */ + BodyType producerIn() default @BodyType; + + /** + * The body after the producer: {@link BodyType.Carrier#UNCHANGED} when it leaves the body as it was, otherwise what + * it sets, such as the response of a call. + */ + BodyType producerOut() default @BodyType; + + /** + * The bodies a data format can marshal. + */ + BodyType marshalIn() default @BodyType; + + /** + * The body after marshal. + */ + BodyType marshalOut() default @BodyType; + + /** + * The bodies a data format can unmarshal. + */ + BodyType unmarshalIn() default @BodyType; + + /** + * The body after unmarshal. + */ + BodyType unmarshalOut() default @BodyType; + + /** + * The bodies the expressions of a language can read. + */ + BodyType expressionIn() default @BodyType; + + /** + * The result of an expression of a language, which becomes the body in setBody and transform. + */ + BodyType expressionOut() default @BodyType; + +} diff --git a/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc b/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc index 7f63d0149d286..ce268d3ee666a 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc @@ -122,6 +122,75 @@ public class MailEndpoint extends ScheduledPollEndpoint { Aliases are descriptive names for discovery only; they cannot be used as the scheme in an endpoint URI. +=== Body types in the Catalog + +The options of a component say how to configure it, but not what the message body is after a step. That is the +question people, IDEs and AI agents ask most when they write the next step of a route: the file consumer gives the +file it read, the `http` producer leaves the response as a byte array or a stream, and after `unmarshal: json` the body +is a `Map` or a `List`. + +A component, data format or language can declare its body types, and the catalog then has a `bodyTypes` section with +one entry per side: + +[width="100%",cols="25%,75%",options="header",] +|=== +|Side |The body +|`consumerOut` |The body of the message the consumer creates. +|`consumerReply` |The bodies a consumer that answers the caller can reply with: the body the route ends with. +|`producerIn` |The bodies the producer can send or write. +|`producerOut` |The body after the producer. +|`marshalIn`, `marshalOut` |The bodies a data format can marshal, and the body after marshal. +|`unmarshalIn`, `unmarshalOut` |The bodies a data format can unmarshal, and the body after unmarshal. +|`expressionIn`, `expressionOut` |The bodies the expressions of a language can read, and their result. +|=== + +Each side has the _carriers_ of the body, the kinds of Java object it is in: `null`, `text`, `bytes`, `stream`, +`file`, `value`, `map`, `list`, `iterator` (read once, with no size and no index) and `pojo`; `unchanged` for a step +that leaves the body as it was, `any` for a step that takes any body, and `unknown` for a body that depends on what +the other system sends or on a class the user configures. A side can also have the Java type when the body is always of that type, the format of the payload (json, +xml, csv, yaml, text or binary), the options the body depends on, the body for other values of an option, and a +description: + +[source,json] +---- +"bodyTypes": { + "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], "format": "xml", "dependsOn": [ "source" ], + "description": "The XML to transform: text, bytes, a stream, a file, a DOM or another javax.xml.transform.Source. ..." }, + "producerOut": { "carriers": [ "text" ], "javaType": "java.lang.String", "dependsOn": [ "resultHandlerFactory" ], + "byOption": [ { "option": "output", "value": "bytes", "carriers": [ "bytes" ], "javaType": "byte[]" }, ... ], + "description": "The result of the transformation" } +} +---- + +The sides say what the body is with the default options. A side that is not in the catalog is not known, and tooling +should not guess it. The Java API gives them on the model: + +[source,java] +---- +CamelCatalog catalog = new DefaultCamelCatalog(); + +BodyTypeModel out = catalog.componentModel("xslt").getBodyType(BodyTypeModel.PRODUCER_OUT); +out.getCarriers(); // [text] +out.getJavaType(); // java.lang.String +---- + +Component developers declare them with the `@BodyTypes` annotation on the endpoint, data format or language class. A +subclass does not have the body types of its superclass, as it may change the body: + +[source,java] +---- +@BodyTypes(consumerOut = @BodyType(value = Carrier.FILE, description = "The file as read ..."), + producerIn = @BodyType({ Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE }), + producerOut = @BodyType(Carrier.UNCHANGED)) +@UriEndpoint(firstVersion = "1.0.0", scheme = "file", ...) +public class FileEndpoint extends GenericFileEndpoint { +---- + +The build checks that each side belongs to the kind (a consumer-only component has no producer sides), that the +options a side names exist, that `unchanged` is only on the body after a step and `any` only on what a step needs. +The values are read from the code that sets the body, and checked against the body types the message history records +when the routes run. + === Documentation in the Catalog The catalog includes the AsciiDoc documentation pages from all modules. This makes the full diff --git a/docs/user-manual/modules/ROOT/pages/endpoint-annotations.adoc b/docs/user-manual/modules/ROOT/pages/endpoint-annotations.adoc index c46b4fedafcd4..aa9b755a18b8a 100644 --- a/docs/user-manual/modules/ROOT/pages/endpoint-annotations.adoc +++ b/docs/user-manual/modules/ROOT/pages/endpoint-annotations.adoc @@ -31,6 +31,10 @@ configuration parameters; then the class of this field should be annotated with one or more `@UriParam` or `@UriParams` annotations |`@Metadata` | Used for special situations to provide additional information. + +|`@BodyTypes` | The message body the endpoint works with: the body the consumer creates, the bodies the +producer can send, and the body after the producer. See xref:camel-catalog.adoc#_body_types_in_the_catalog[Body types +in the Catalog]. |======================================================================= For example see the `TimerEndpoint` from the `camel-timer` component and diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java index f780f04260069..b9b988c5372a5 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java @@ -66,6 +66,14 @@ public final class CatalogDocs { /** The sub-pages of a language's documentation (simple has functions, operators, ognl and advanced). */ private static final List LANGUAGE_DOC_PAGES = List.of("functions", "operators", "ognl", "advanced"); + static final String BODY_TYPES_HINT = "the message body with the default options. consumerOut: the body a from:" + + " gives the route; consumerReply: the bodies it can reply with; producerIn:" + + " the bodies a to: can send; producerOut: the body after the to:; marshal," + + " unmarshal and expression In/Out: the same for a data format and a" + + " language. Carriers: text, bytes, stream, file, value, map, list," + + " iterator (no size, no index), pojo, null, unchanged, any, unknown. A side" + + " not listed is not known"; + /** * The rules a small model gets wrong most: functions live inside the placeholder, operators between placeholders. * Sent with the simple language result so an answer's examples follow the same shape as the catalog's. @@ -1626,6 +1634,11 @@ private static void addCommonModelFields(JsonObject result, BaseModel model) result.put("deprecationNote", model.getDeprecationNote()); } } + if (!model.getBodyTypes().isEmpty()) { + // what the body is after the step and what it needs, which the options do not say (CAMEL-24961) + result.put("bodyTypes", JsonMapper.asJsonObjectBodyTypes(model.getBodyTypes())); + result.put("bodyTypesHint", BODY_TYPES_HINT); + } } /** diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java index ae686c0d8080d..87de31b39b658 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java @@ -133,6 +133,25 @@ void componentOptionsSayWhetherTheyArePathOrQueryAndTheUriRulesComeAlong() throw assertTrue(direct.getString("uriSyntax").contains("(name) go in the path")); } + @Test + void theBodyTypesSayWhatTheBodyIsAfterAStep() throws Exception { + // CAMEL-24961: what the options of a component do not say + JsonObject xslt = catalogDoc(Map.of("name", "xslt", "kind", "component", "includeOptions", false)); + JsonObject out = ((JsonObject) xslt.get("bodyTypes")).getMap("producerOut"); + assertEquals(List.of("text"), out.getCollection("carriers")); + assertEquals("java.lang.String", out.getString("javaType")); + assertTrue(xslt.getString("bodyTypesHint").contains("A side not listed is not known")); + + JsonObject json = catalogDoc(Map.of("name", "json", "kind", "dataformat", "includeOptions", false)); + JsonObject unmarshalOut = ((JsonObject) json.get("bodyTypes")).getMap("unmarshalOut"); + assertEquals(List.of("map", "list"), unmarshalOut.getCollection("carriers")); + + // a component that does not declare them has none, rather than a guess + JsonObject kafka = catalogDoc(Map.of("name", "kafka", "kind", "component", "includeOptions", false)); + assertNull(kafka.get("bodyTypes")); + assertNull(kafka.get("bodyTypesHint")); + } + @Test void endpointArgumentValidatesAUriAgainstTheComponent() throws Exception { JsonObject bad = catalogDoc(Map.of("endpoint", "timer:tick?period=5s&fixedRte=true&repeatCount=abc")); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/support/Schema.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/support/Schema.java index ece10f183c110..cc7b9bf26e2a5 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/support/Schema.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/support/Schema.java @@ -30,6 +30,8 @@ public class Schema { public ObjectNode properties; @JsonIgnore public ObjectNode exchangeProperties; + @JsonIgnore + public ObjectNode bodyTypes; @JsonProperty public ObjectNode functions; @JsonProperty diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BaseModel.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BaseModel.java index 7e08abe942402..381aeb9412a6d 100644 --- a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BaseModel.java +++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BaseModel.java @@ -38,6 +38,7 @@ public abstract class BaseModel { protected boolean nativeSupported; protected List aliases = new ArrayList<>(); protected Map metadata = new LinkedHashMap<>(); + protected Map bodyTypes = new LinkedHashMap<>(); public static Comparator> compareTitle() { return (m1, m2) -> m1.getTitle().compareToIgnoreCase(m2.getTitle()); @@ -175,6 +176,26 @@ public void setMetadata(Map metadata) { this.metadata = metadata; } + /** + * The body types of a component, data format or language, by side ({@link BodyTypeModel#CONSUMER_OUT}, + * {@link BodyTypeModel#PRODUCER_IN}...): the body a step needs and the body it leaves. A side that is not here is + * not known. + */ + public Map getBodyTypes() { + return bodyTypes; + } + + public void setBodyTypes(Map bodyTypes) { + this.bodyTypes = bodyTypes; + } + + /** + * The body type of a side, such as {@link BodyTypeModel#PRODUCER_OUT}, or null when it is not known. + */ + public BodyTypeModel getBodyType(String side) { + return bodyTypes.get(side); + } + @Override public String toString() { return name; diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeModel.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeModel.java new file mode 100644 index 0000000000000..d9097160e19c6 --- /dev/null +++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/BodyTypeModel.java @@ -0,0 +1,195 @@ +/* + * 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.ArrayList; +import java.util.List; + +/** + * One side of the body types of a component, data format or language: the body a step needs, or the body it leaves + * after it, with the default options (CAMEL-24961). + *

+ * The carriers are the kinds of Java object the body is in: null, text, bytes, stream, file, value, map, list, iterator + * and pojo; unchanged for a step that leaves the body as it was, any for a step that takes any body, and unknown for a + * body that depends on what the other system sends or on a class the user configures. + */ +public class BodyTypeModel { + + /** The body of the message the consumer creates. */ + public static final String CONSUMER_OUT = "consumerOut"; + /** The bodies a consumer that answers the caller can reply with: the body the route ends with. */ + public static final String CONSUMER_REPLY = "consumerReply"; + /** The bodies the producer can send or write. */ + public static final String PRODUCER_IN = "producerIn"; + /** The body after the producer. */ + public static final String PRODUCER_OUT = "producerOut"; + /** The bodies a data format can marshal. */ + public static final String MARSHAL_IN = "marshalIn"; + /** The body after marshal. */ + public static final String MARSHAL_OUT = "marshalOut"; + /** The bodies a data format can unmarshal. */ + public static final String UNMARSHAL_IN = "unmarshalIn"; + /** The body after unmarshal. */ + public static final String UNMARSHAL_OUT = "unmarshalOut"; + /** The bodies the expressions of a language can read. */ + public static final String EXPRESSION_IN = "expressionIn"; + /** The result of an expression of a language. */ + public static final String EXPRESSION_OUT = "expressionOut"; + + private List carriers = new ArrayList<>(); + private String javaType; + private String format; + private List dependsOn = new ArrayList<>(); + private List byOption = new ArrayList<>(); + private String description; + + /** + * The carriers of the body: one, or several for a body that can be any of them. + */ + public List getCarriers() { + return carriers; + } + + public void setCarriers(List carriers) { + this.carriers = carriers; + } + + /** + * The Java type of the body, when it is always of this type. + */ + public String getJavaType() { + return javaType; + } + + public void setJavaType(String javaType) { + this.javaType = javaType; + } + + /** + * The format of the payload, when it is always the same: json, xml, csv, yaml, text or binary. + */ + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + /** + * The options the body depends on: with another value than the default the body is not known, unless + * {@link #getByOption()} says it. + */ + public List getDependsOn() { + return dependsOn; + } + + public void setDependsOn(List dependsOn) { + this.dependsOn = dependsOn; + } + + /** + * The body for other values of an option. + */ + public List getByOption() { + return byOption; + } + + public void setByOption(List byOption) { + this.byOption = byOption; + } + + /** + * What to know about the body, in a sentence. + */ + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * Whether the body is left as it was (for the body after a step). + */ + public boolean isUnchanged() { + return carriers.contains("unchanged"); + } + + /** + * Whether the body is not known without the configuration or the data of the other system. A side with no carriers + * is not known either: the build rejects one, but a catalog from elsewhere, such as JSON written by hand, can have + * it. + */ + public boolean isUnknown() { + return carriers.isEmpty() || carriers.contains("unknown"); + } + + @Override + public String toString() { + return String.join(", ", carriers) + (javaType != null ? " (" + javaType + ")" : ""); + } + + /** + * The body when an option has a value. + */ + public static class OptionBodyTypeModel { + + private String option; + private String value; + private List carriers = new ArrayList<>(); + private String javaType; + + public String getOption() { + return option; + } + + public void setOption(String option) { + this.option = option; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getCarriers() { + return carriers; + } + + public void setCarriers(List carriers) { + this.carriers = carriers; + } + + public String getJavaType() { + return javaType; + } + + public void setJavaType(String javaType) { + this.javaType = javaType; + } + + @Override + public String toString() { + return option + "=" + value + ": " + String.join(", ", carriers); + } + } +} 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 ac23618bc572f..28e0ab4a28345 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 @@ -19,7 +19,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -97,6 +99,7 @@ public static ComponentModel generateComponentModel(JsonObject obj) { JsonObject mobj = (JsonObject) obj.get("component"); ComponentModel model = new ComponentModel(); parseComponentModel(mobj, model); + model.setBodyTypes(parseBodyTypes(obj.getMap("bodyTypes"))); JsonObject mcprp = (JsonObject) obj.get("componentProperties"); if (mcprp != null) { for (Map.Entry entry : mcprp.entrySet()) { @@ -252,6 +255,7 @@ public static JsonObject asJsonObject(ComponentModel model) { if (!headers.isEmpty()) { wrapper.put("headers", asJsonObject(headers)); } + bodyTypesToJson(model, wrapper); wrapper.put("properties", asJsonObject(model.getEndpointOptions())); if (!model.getApiOptions().isEmpty()) { wrapper.put("apis", apiModelAsJsonObject(model.getApiOptions(), false)); @@ -269,6 +273,7 @@ public static DataFormatModel generateDataFormatModel(JsonObject obj) { JsonObject mobj = (JsonObject) obj.get("dataformat"); DataFormatModel model = new DataFormatModel(); parseModel(mobj, model); + model.setBodyTypes(parseBodyTypes(obj.getMap("bodyTypes"))); parseArtifact(mobj, model); model.setModelName(mobj.getString("modelName")); model.setModelJavaType(mobj.getString("modelJavaType")); @@ -297,6 +302,7 @@ public static JsonObject asJsonObject(DataFormatModel model) { obj.entrySet().removeIf(e -> e.getValue() == null); JsonObject wrapper = new JsonObject(); wrapper.put("dataformat", obj); + bodyTypesToJson(model, wrapper); wrapper.put("properties", asJsonObject(model.getOptions())); return wrapper; } @@ -478,6 +484,7 @@ public static LanguageModel generateLanguageModel(JsonObject obj) { JsonObject mobj = (JsonObject) obj.get("language"); LanguageModel model = new LanguageModel(); parseModel(mobj, model); + model.setBodyTypes(parseBodyTypes(obj.getMap("bodyTypes"))); model.setModelName(mobj.getString("modelName")); model.setModelJavaType(mobj.getString("modelJavaType")); parseArtifact(mobj, model); @@ -523,6 +530,7 @@ public static JsonObject asJsonObject(LanguageModel model) { obj.entrySet().removeIf(e -> e.getValue() == null); JsonObject wrapper = new JsonObject(); wrapper.put("language", obj); + bodyTypesToJson(model, wrapper); wrapper.put("properties", asJsonObject(model.getOptions())); final List functions = model.getFunctions(); if (!functions.isEmpty()) { @@ -693,6 +701,84 @@ private static void parseModel(JsonObject mobj, BaseModel model) { model.setMetadata(mobj.getMapOrDefault("metadata", new JsonObject())); } + /** The body types of a component, data format or language: one object per side (CAMEL-24961). */ + private static Map parseBodyTypes(JsonObject json) { + Map answer = new LinkedHashMap<>(); + if (json == null) { + return answer; + } + for (Map.Entry entry : json.entrySet()) { + JsonObject side = (JsonObject) entry.getValue(); + BodyTypeModel bodyType = new BodyTypeModel(); + bodyType.setCarriers(stringList(side.getCollection("carriers"))); + bodyType.setJavaType(side.getString("javaType")); + bodyType.setFormat(side.getString("format")); + bodyType.setDependsOn(stringList(side.getCollection("dependsOn"))); + Collection byOption = side.getCollection("byOption"); + if (byOption != null) { + for (JsonObject when : byOption) { + BodyTypeModel.OptionBodyTypeModel option = new BodyTypeModel.OptionBodyTypeModel(); + option.setOption(when.getString("option")); + option.setValue(when.getString("value")); + option.setCarriers(stringList(when.getCollection("carriers"))); + option.setJavaType(when.getString("javaType")); + bodyType.getByOption().add(option); + } + } + bodyType.setDescription(side.getString("description")); + answer.put(entry.getKey(), bodyType); + } + return answer; + } + + private static List stringList(Collection values) { + return values != null ? new ArrayList<>(values) : new ArrayList<>(); + } + + /** The body types as a section of their own, next to the options, so each side is a line of the catalog. */ + private static void bodyTypesToJson(BaseModel model, JsonObject wrapper) { + if (model.getBodyTypes().isEmpty()) { + return; + } + wrapper.put("bodyTypes", asJsonObjectBodyTypes(model.getBodyTypes())); + } + + /** + * The body types of a component, data format or language as the catalog has them: an object per side, with the + * carriers, the Java type, the format, the options it depends on, the body for other option values and the + * description (CAMEL-24961). + */ + public static JsonObject asJsonObjectBodyTypes(Map bodyTypes) { + JsonObject json = new JsonObject(); + for (Map.Entry entry : bodyTypes.entrySet()) { + BodyTypeModel bodyType = entry.getValue(); + JsonObject side = new JsonObject(); + side.put("carriers", bodyType.getCarriers()); + side.put("javaType", bodyType.getJavaType()); + side.put("format", bodyType.getFormat()); + if (!bodyType.getDependsOn().isEmpty()) { + side.put("dependsOn", bodyType.getDependsOn()); + } + if (!bodyType.getByOption().isEmpty()) { + JsonArray byOption = new JsonArray(); + for (BodyTypeModel.OptionBodyTypeModel option : bodyType.getByOption()) { + JsonObject when = new JsonObject(); + when.put("option", option.getOption()); + when.put("value", option.getValue()); + when.put("carriers", option.getCarriers()); + when.put("javaType", option.getJavaType()); + when.entrySet().removeIf(e -> e.getValue() == null); + byOption.add(when); + } + side.put("byOption", byOption); + } + side.put("description", bodyType.getDescription()); + side.entrySet().removeIf(e -> e.getValue() == null); + json.put(entry.getKey(), side); + } + return json; + } + private static void parseOption(JsonObject mp, BaseOptionModel option, String name) { option.setName(name); Integer idx = mp.getInteger("index"); diff --git a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java index c97f45b0b7d22..0b5fdc875bc14 100644 --- a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java +++ b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java @@ -23,6 +23,7 @@ import static org.apache.camel.tooling.model.ComponentModel.EndpointHeaderModel; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -161,4 +162,81 @@ void testShouldSerializeAndDeserializeApiReference() { assertEquals(List.of("Object getBody()", " T getBody(Class type)"), m.getSignatures()); assertEquals(List.of("message.getBody(String.class)"), m.getExamples()); } + + @Test + void testShouldWriteBodyTypesOnlyWhenSet() { + // CAMEL-24961: the body a step needs and the body it leaves + ComponentModel model = new ComponentModel(); + String json = JsonMapper.createParameterJsonSchema(model); + assertFalse(json.contains("\"bodyTypes\"")); + assertTrue(JsonMapper.generateComponentModel(json).getBodyTypes().isEmpty()); + + BodyTypeModel in = new BodyTypeModel(); + in.setCarriers(List.of("text", "bytes", "stream", "file")); + in.setFormat("xml"); + BodyTypeModel out = new BodyTypeModel(); + out.setCarriers(List.of("text")); + out.setJavaType("java.lang.String"); + out.setDependsOn(List.of("resultHandlerFactory")); + BodyTypeModel.OptionBodyTypeModel bytes = new BodyTypeModel.OptionBodyTypeModel(); + bytes.setOption("output"); + bytes.setValue("bytes"); + bytes.setCarriers(List.of("bytes")); + bytes.setJavaType("byte[]"); + out.getByOption().add(bytes); + out.setDescription("The result of the transformation"); + model.getBodyTypes().put(BodyTypeModel.PRODUCER_IN, in); + model.getBodyTypes().put(BodyTypeModel.PRODUCER_OUT, out); + + json = JsonMapper.createParameterJsonSchema(model); + assertTrue(json.contains("\"bodyTypes\""), json); + ComponentModel model2 = JsonMapper.generateComponentModel(json); + assertEquals(List.of(BodyTypeModel.PRODUCER_IN, BodyTypeModel.PRODUCER_OUT), + List.copyOf(model2.getBodyTypes().keySet())); + assertNull(model2.getBodyType(BodyTypeModel.CONSUMER_OUT)); + + BodyTypeModel in2 = model2.getBodyType(BodyTypeModel.PRODUCER_IN); + assertEquals(List.of("text", "bytes", "stream", "file"), in2.getCarriers()); + assertEquals("xml", in2.getFormat()); + assertNull(in2.getJavaType()); + assertTrue(in2.getDependsOn().isEmpty()); + assertTrue(in2.getByOption().isEmpty()); + + BodyTypeModel out2 = model2.getBodyType(BodyTypeModel.PRODUCER_OUT); + assertEquals(List.of("text"), out2.getCarriers()); + assertEquals("java.lang.String", out2.getJavaType()); + assertEquals(List.of("resultHandlerFactory"), out2.getDependsOn()); + assertEquals("The result of the transformation", out2.getDescription()); + assertEquals(1, out2.getByOption().size()); + BodyTypeModel.OptionBodyTypeModel bytes2 = out2.getByOption().get(0); + assertEquals("output", bytes2.getOption()); + assertEquals("bytes", bytes2.getValue()); + assertEquals(List.of("bytes"), bytes2.getCarriers()); + assertEquals("byte[]", bytes2.getJavaType()); + assertFalse(out2.isUnchanged()); + assertFalse(out2.isUnknown()); + } + + @Test + void testShouldWriteBodyTypesOfDataFormatsAndLanguages() { + DataFormatModel dataFormat = new DataFormatModel(); + dataFormat.setName("jackson"); + BodyTypeModel unmarshalOut = new BodyTypeModel(); + unmarshalOut.setCarriers(List.of("map", "list")); + unmarshalOut.setDependsOn(List.of("unmarshalType")); + dataFormat.getBodyTypes().put(BodyTypeModel.UNMARSHAL_OUT, unmarshalOut); + DataFormatModel dataFormat2 + = JsonMapper.generateDataFormatModel(JsonMapper.createParameterJsonSchema(dataFormat)); + assertEquals(List.of("map", "list"), dataFormat2.getBodyType(BodyTypeModel.UNMARSHAL_OUT).getCarriers()); + assertEquals(List.of("unmarshalType"), dataFormat2.getBodyType(BodyTypeModel.UNMARSHAL_OUT).getDependsOn()); + + LanguageModel language = new LanguageModel(); + language.setName("constant"); + BodyTypeModel expressionOut = new BodyTypeModel(); + expressionOut.setCarriers(List.of("text")); + language.getBodyTypes().put(BodyTypeModel.EXPRESSION_OUT, expressionOut); + LanguageModel language2 = JsonMapper.generateLanguageModel(JsonMapper.createParameterJsonSchema(language)); + assertEquals(List.of("text"), language2.getBodyType(BodyTypeModel.EXPRESSION_OUT).getCarriers()); + assertNull(language2.getBodyType(BodyTypeModel.EXPRESSION_IN)); + } } diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java new file mode 100644 index 0000000000000..fcb3789aa78a5 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/BodyTypesHelper.java @@ -0,0 +1,189 @@ +/* + * 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.maven.packaging; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; +import org.apache.camel.tooling.model.BaseModel; +import org.apache.camel.tooling.model.BodyTypeModel; +import org.apache.camel.tooling.model.ComponentModel; + +/** + * Carries the {@link BodyTypes} of a component, data format or language into its model, which the catalog JSON is + * written from (CAMEL-24961). + */ +final class BodyTypesHelper { + + private static final Set IN = Set.of(BodyTypeModel.CONSUMER_REPLY, BodyTypeModel.PRODUCER_IN, + BodyTypeModel.MARSHAL_IN, BodyTypeModel.UNMARSHAL_IN, BodyTypeModel.EXPRESSION_IN); + + private BodyTypesHelper() { + } + + /** + * Adds the sides the annotation gives to the model, in the order of the annotation. + * + * @param model the model of the component, data format or language + * @param bodyTypes the annotation, null when the class has none + * @param owner the class with the annotation, for the error message + * @throws IllegalArgumentException when a side does not fit the model: a data format side on a component, a + * producer side on a consumer-only component, unchanged on what a step needs or + * any on what it leaves + */ + static void addBodyTypes(BaseModel model, BodyTypes bodyTypes, Class owner) { + if (bodyTypes == null) { + return; + } + Map sides = new LinkedHashMap<>(); + sides.put(BodyTypeModel.CONSUMER_OUT, bodyTypes.consumerOut()); + sides.put(BodyTypeModel.CONSUMER_REPLY, bodyTypes.consumerReply()); + sides.put(BodyTypeModel.PRODUCER_IN, bodyTypes.producerIn()); + sides.put(BodyTypeModel.PRODUCER_OUT, bodyTypes.producerOut()); + sides.put(BodyTypeModel.MARSHAL_IN, bodyTypes.marshalIn()); + sides.put(BodyTypeModel.MARSHAL_OUT, bodyTypes.marshalOut()); + sides.put(BodyTypeModel.UNMARSHAL_IN, bodyTypes.unmarshalIn()); + sides.put(BodyTypeModel.UNMARSHAL_OUT, bodyTypes.unmarshalOut()); + sides.put(BodyTypeModel.EXPRESSION_IN, bodyTypes.expressionIn()); + sides.put(BodyTypeModel.EXPRESSION_OUT, bodyTypes.expressionOut()); + + Set allowed = allowedSides(model); + Set options = optionNames(model); + for (Map.Entry entry : sides.entrySet()) { + String side = entry.getKey(); + BodyType bodyType = entry.getValue(); + if (bodyType.value().length == 0) { + if (!bodyType.javaType().isEmpty() || !bodyType.format().isEmpty() || bodyType.dependsOn().length > 0 + || bodyType.byOption().length > 0 || !bodyType.description().isEmpty()) { + throw new IllegalArgumentException( + owner.getName() + ": the @BodyTypes " + side + " has no carriers, but other attributes"); + } + continue; + } + if (!allowed.contains(side)) { + throw new IllegalArgumentException( + owner.getName() + ": the @BodyTypes " + side + " is not a side of this " + model.getKind() + + ", which has " + allowed); + } + check(owner, side, bodyType.value()); + for (String option : bodyType.dependsOn()) { + checkOption(owner, side, option, options); + } + for (BodyType.When when : bodyType.byOption()) { + checkOption(owner, side, when.option(), options); + check(owner, side + " " + when.option() + "=" + when.value(), when.carriers()); + } + model.getBodyTypes().put(side, asModel(bodyType)); + } + } + + private static Set allowedSides(BaseModel model) { + return switch (model.getKind()) { + case component -> { + ComponentModel component = (ComponentModel) model; + if (component.isConsumerOnly()) { + yield Set.of(BodyTypeModel.CONSUMER_OUT, BodyTypeModel.CONSUMER_REPLY); + } else if (component.isProducerOnly()) { + yield Set.of(BodyTypeModel.PRODUCER_IN, BodyTypeModel.PRODUCER_OUT); + } + yield Set.of(BodyTypeModel.CONSUMER_OUT, BodyTypeModel.CONSUMER_REPLY, BodyTypeModel.PRODUCER_IN, + BodyTypeModel.PRODUCER_OUT); + } + case dataformat -> Set.of(BodyTypeModel.MARSHAL_IN, BodyTypeModel.MARSHAL_OUT, BodyTypeModel.UNMARSHAL_IN, + BodyTypeModel.UNMARSHAL_OUT); + case language -> Set.of(BodyTypeModel.EXPRESSION_IN, BodyTypeModel.EXPRESSION_OUT); + default -> Set.of(); + }; + } + + /** The options of the model, which the body types can depend on. */ + private static Set optionNames(BaseModel model) { + Set names = new HashSet<>(); + model.getOptions().forEach(o -> names.add(o.getName())); + if (model instanceof ComponentModel component) { + component.getComponentOptions().forEach(o -> names.add(o.getName())); + component.getEndpointOptions().forEach(o -> names.add(o.getName())); + } + return names; + } + + private static void checkOption(Class owner, String side, String option, Set options) { + if (!options.contains(option)) { + throw new IllegalArgumentException( + owner.getName() + ": the @BodyTypes " + side + " depends on " + option + ", which is not an option"); + } + } + + /** + * A body has a carrier: an option value without one would say nothing. UNCHANGED is for the body after a step, such + * as a sql query that leaves it unchanged unless it is a select. ANY is for the bodies a step needs. ANY and + * UNKNOWN stand alone. + */ + private static void check(Class owner, String side, Carrier[] carriers) { + if (carriers.length == 0) { + throw new IllegalArgumentException(owner.getName() + ": the @BodyTypes " + side + " has no carriers"); + } + List list = Arrays.asList(carriers); + boolean in = IN.contains(side.split(" ")[0]); + if (in && list.contains(Carrier.UNCHANGED)) { + throw new IllegalArgumentException( + owner.getName() + ": the @BodyTypes " + side + " is what a step needs, which cannot be unchanged"); + } + if (!in && list.contains(Carrier.ANY)) { + throw new IllegalArgumentException( + owner.getName() + ": the @BodyTypes " + side + " is the body after a step, which cannot be any"); + } + if (list.size() > 1 && (list.contains(Carrier.ANY) || list.contains(Carrier.UNKNOWN))) { + throw new IllegalArgumentException( + owner.getName() + ": the @BodyTypes " + side + " has any or unknown with other carriers"); + } + } + + private static BodyTypeModel asModel(BodyType bodyType) { + BodyTypeModel model = new BodyTypeModel(); + model.setCarriers(names(bodyType.value())); + model.setJavaType(emptyAsNull(bodyType.javaType())); + model.setFormat(emptyAsNull(bodyType.format())); + model.setDependsOn(new ArrayList<>(Arrays.asList(bodyType.dependsOn()))); + for (BodyType.When when : bodyType.byOption()) { + BodyTypeModel.OptionBodyTypeModel option = new BodyTypeModel.OptionBodyTypeModel(); + option.setOption(when.option()); + option.setValue(when.value()); + option.setCarriers(names(when.carriers())); + option.setJavaType(emptyAsNull(when.javaType())); + model.getByOption().add(option); + } + model.setDescription(emptyAsNull(bodyType.description())); + return model; + } + + private static List names(Carrier[] carriers) { + return new ArrayList<>(Arrays.stream(carriers).map(Carrier::getValue).toList()); + } + + private static String emptyAsNull(String value) { + return value.isEmpty() ? null : value; + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java index 061b650e06b54..6621f1022b577 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java @@ -52,6 +52,7 @@ import org.apache.camel.spi.ApiMethod; import org.apache.camel.spi.ApiParam; import org.apache.camel.spi.ApiParams; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -306,6 +307,7 @@ protected ComponentModel writeJSonSchemeAndPropertyConfigurer( SchemaHelper.addModelMetadata(componentModel, project); SchemaHelper.addModelMetadata(componentModel, classElement.getAnnotation(Metadata.class)); + addBodyTypes(componentModel, classElement); String json = JsonMapper.createParameterJsonSchema(componentModel); @@ -322,6 +324,18 @@ protected ComponentModel writeJSonSchemeAndPropertyConfigurer( return componentModel; } + /** + * Adds the body types of the endpoint, which the endpoint class or a superclass declares with {@link BodyTypes} + * (CAMEL-24961). + */ + void addBodyTypes(ComponentModel componentModel, Class classElement) throws MojoExecutionException { + try { + BodyTypesHelper.addBodyTypes(componentModel, classElement.getAnnotation(BodyTypes.class), classElement); + } catch (IllegalArgumentException e) { + throw new MojoExecutionException(e.getMessage(), e); + } + } + /** * Retrieve the metadata added to all the {@code String} constants defined in the class corresponding to the element * {@code headersClass} of the annotation {@code UriEndpoint} along with all its super classes and implemented diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PackageDataFormatMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PackageDataFormatMojo.java index edc311d02b7ad..66377f9c3cf08 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PackageDataFormatMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PackageDataFormatMojo.java @@ -35,6 +35,7 @@ import javax.inject.Inject; import org.apache.camel.maven.packaging.generics.ClassUtil; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.tooling.model.BaseOptionModel; import org.apache.camel.tooling.model.DataFormatModel; @@ -202,6 +203,7 @@ public int prepareDataFormat() throws MojoExecutionException { SchemaHelper.addModelMetadata(dataFormatModel, project); SchemaHelper.addModelMetadata(dataFormatModel, metadata); + BodyTypesHelper.addBodyTypes(dataFormatModel, clazz.getAnnotation(BodyTypes.class), clazz); if (!modelName.equals(name)) { /* Prefer description from the clazz */ diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PackageLanguageMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PackageLanguageMojo.java index 94f1f3974bc89..862a093d70655 100644 --- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PackageLanguageMojo.java +++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/PackageLanguageMojo.java @@ -29,6 +29,7 @@ import javax.inject.Inject; import org.apache.camel.maven.packaging.generics.ClassUtil; +import org.apache.camel.spi.BodyTypes; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.annotations.Language; import org.apache.camel.tooling.model.EipModel; @@ -166,6 +167,7 @@ public int prepareLanguage() throws MojoExecutionException { SchemaHelper.addModelMetadata(languageModel, project); SchemaHelper.addModelMetadata(languageModel, javaType.getAnnotation(Metadata.class)); + BodyTypesHelper.addBodyTypes(languageModel, javaType.getAnnotation(BodyTypes.class), javaType); // build json schema for the language String schema = JsonMapper.createParameterJsonSchema(languageModel); diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java index 562fce01273ec..5fa2fe71426d0 100644 --- a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java +++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java @@ -20,11 +20,14 @@ import java.util.List; import org.apache.camel.maven.packaging.endpoint.SomeEndpoint; +import org.apache.camel.maven.packaging.endpoint.SomeEndpointExtendingBodyTypes; import org.apache.camel.maven.packaging.endpoint.SomeEndpointUsingEnumConstants; import org.apache.camel.maven.packaging.endpoint.SomeEndpointUsingEnumConstantsByField; import org.apache.camel.maven.packaging.endpoint.SomeEndpointUsingEnumConstantsByMethod; import org.apache.camel.maven.packaging.endpoint.SomeEndpointUsingInterfaceConstants; +import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithBadBodyTypes; import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithBadHeaders; +import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithBodyTypes; import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithFilter; import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithHeaderClassHierarchy; import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithHeaderInterfaceHierarchy; @@ -33,6 +36,7 @@ import org.apache.camel.maven.packaging.endpoint.SomeExtendingEndpoint; import org.apache.camel.maven.packaging.endpoint.SomeInternalRemoteEndpoint; import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.tooling.model.BodyTypeModel; import org.apache.camel.tooling.model.ComponentModel; import org.apache.camel.tooling.model.ComponentModel.EndpointHeaderModel; import org.apache.maven.plugin.MojoExecutionException; @@ -225,4 +229,69 @@ void testEndpointWithHeadersOnSelfAndSuperclass() { assertEquals("KEY_EMPTY_WITH_JAVA_DOC", headerFromParent.getName()); } + + @Test + void testEndpointWithBodyTypes() throws Exception { + // CAMEL-24961: the body a step needs and the body it leaves, in the order of the annotation + addEndpointOptions("resultHandlerFactory", "output"); + mojo.addBodyTypes(model, SomeEndpointWithBodyTypes.class); + assertEquals(List.of(BodyTypeModel.CONSUMER_OUT, BodyTypeModel.PRODUCER_IN, BodyTypeModel.PRODUCER_OUT), + List.copyOf(model.getBodyTypes().keySet())); + + BodyTypeModel consumerOut = model.getBodyType(BodyTypeModel.CONSUMER_OUT); + assertEquals(List.of("file"), consumerOut.getCarriers()); + assertEquals("org.apache.camel.component.file.GenericFile", consumerOut.getJavaType()); + assertEquals("The file as read", consumerOut.getDescription()); + assertNull(consumerOut.getFormat()); + assertTrue(consumerOut.getDependsOn().isEmpty()); + + assertEquals(List.of("text", "bytes", "stream", "file"), + model.getBodyType(BodyTypeModel.PRODUCER_IN).getCarriers()); + + BodyTypeModel producerOut = model.getBodyType(BodyTypeModel.PRODUCER_OUT); + assertEquals(List.of("text"), producerOut.getCarriers()); + assertEquals("xml", producerOut.getFormat()); + assertEquals(List.of("resultHandlerFactory"), producerOut.getDependsOn()); + assertEquals(1, producerOut.getByOption().size()); + BodyTypeModel.OptionBodyTypeModel bytes = producerOut.getByOption().get(0); + assertEquals("output", bytes.getOption()); + assertEquals("bytes", bytes.getValue()); + assertEquals(List.of("bytes"), bytes.getCarriers()); + assertEquals("byte[]", bytes.getJavaType()); + } + + @Test + void testEndpointDoesNotInheritBodyTypes() throws Exception { + // a subclass may change the body, so it declares its own + mojo.addBodyTypes(model, SomeEndpointExtendingBodyTypes.class); + assertTrue(model.getBodyTypes().isEmpty()); + } + + @Test + void testEndpointWithoutBodyTypes() throws Exception { + mojo.addBodyTypes(model, SomeEndpointWithoutHeaders.class); + assertTrue(model.getBodyTypes().isEmpty()); + } + + @ParameterizedTest + @ValueSource(classes = { + SomeEndpointWithBadBodyTypes.DataFormatSide.class, SomeEndpointWithBadBodyTypes.ProducerSideOfConsumer.class, + SomeEndpointWithBadBodyTypes.UnchangedInput.class, SomeEndpointWithBadBodyTypes.AnyOutput.class, + SomeEndpointWithBadBodyTypes.UnknownWithOthers.class, SomeEndpointWithBadBodyTypes.NoCarriers.class, + SomeEndpointWithBadBodyTypes.UnknownOption.class, SomeEndpointWithBadBodyTypes.OptionValueWithoutCarriers.class }) + void testEndpointWithBadBodyTypes(Class clazz) { + addEndpointOptions("output"); + model.setConsumerOnly(clazz.getAnnotation(UriEndpoint.class).consumerOnly()); + MojoExecutionException e = assertThrows(MojoExecutionException.class, () -> mojo.addBodyTypes(model, clazz)); + assertTrue(e.getMessage().startsWith(clazz.getName() + ": the @BodyTypes "), e.getMessage()); + assertTrue(model.getBodyTypes().isEmpty()); + } + + private void addEndpointOptions(String... names) { + for (String name : names) { + ComponentModel.EndpointOptionModel option = new ComponentModel.EndpointOptionModel(); + option.setName(name); + model.addEndpointOption(option); + } + } } diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointExtendingBodyTypes.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointExtendingBodyTypes.java new file mode 100644 index 0000000000000..31f9aa9563d84 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointExtendingBodyTypes.java @@ -0,0 +1,23 @@ +/* + * 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.maven.packaging.endpoint; + +import org.apache.camel.spi.UriEndpoint; + +@UriEndpoint(scheme = "someextbody", syntax = "someextbody", title = "someextbody") +public class SomeEndpointExtendingBodyTypes extends SomeEndpointWithBodyTypes { +} diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadBodyTypes.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadBodyTypes.java new file mode 100644 index 0000000000000..cccd18faa7155 --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadBodyTypes.java @@ -0,0 +1,72 @@ +/* + * 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.maven.packaging.endpoint; + +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; +import org.apache.camel.spi.UriEndpoint; + +/** + * Body types that do not fit an endpoint, one per nested class. + */ +public final class SomeEndpointWithBadBodyTypes { + + private SomeEndpointWithBadBodyTypes() { + } + + @UriEndpoint(scheme = "some", syntax = "some", title = "some") + @BodyTypes(marshalOut = @BodyType(Carrier.STREAM)) + public static class DataFormatSide { + } + + @UriEndpoint(scheme = "some", syntax = "some", title = "some", consumerOnly = true) + @BodyTypes(producerOut = @BodyType(Carrier.UNCHANGED)) + public static class ProducerSideOfConsumer { + } + + @UriEndpoint(scheme = "some", syntax = "some", title = "some") + @BodyTypes(producerIn = @BodyType(Carrier.UNCHANGED)) + public static class UnchangedInput { + } + + @UriEndpoint(scheme = "some", syntax = "some", title = "some") + @BodyTypes(producerOut = @BodyType(Carrier.ANY)) + public static class AnyOutput { + } + + @UriEndpoint(scheme = "some", syntax = "some", title = "some") + @BodyTypes(producerOut = @BodyType({ Carrier.UNKNOWN, Carrier.TEXT })) + public static class UnknownWithOthers { + } + + @UriEndpoint(scheme = "some", syntax = "some", title = "some") + @BodyTypes(producerOut = @BodyType(javaType = "java.lang.String")) + public static class NoCarriers { + } + + @UriEndpoint(scheme = "some", syntax = "some", title = "some") + @BodyTypes(producerOut = @BodyType(value = Carrier.TEXT, dependsOn = "nope")) + public static class UnknownOption { + } + + @UriEndpoint(scheme = "some", syntax = "some", title = "some") + @BodyTypes(producerOut = @BodyType(value = Carrier.TEXT, + byOption = @BodyType.When(option = "output", value = "bytes", carriers = {}))) + public static class OptionValueWithoutCarriers { + } +} diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBodyTypes.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBodyTypes.java new file mode 100644 index 0000000000000..3473dc6f438cf --- /dev/null +++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBodyTypes.java @@ -0,0 +1,33 @@ +/* + * 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.maven.packaging.endpoint; + +import org.apache.camel.spi.BodyType; +import org.apache.camel.spi.BodyType.Carrier; +import org.apache.camel.spi.BodyTypes; +import org.apache.camel.spi.UriEndpoint; + +@UriEndpoint(scheme = "some", syntax = "some", title = "some") +@BodyTypes(consumerOut = @BodyType(value = Carrier.FILE, javaType = "org.apache.camel.component.file.GenericFile", + description = "The file as read"), + producerIn = @BodyType({ Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE }), + producerOut = @BodyType(value = Carrier.TEXT, javaType = "java.lang.String", format = "xml", + dependsOn = "resultHandlerFactory", + byOption = @BodyType.When(option = "output", value = "bytes", + carriers = Carrier.BYTES, javaType = "byte[]"))) +public class SomeEndpointWithBodyTypes { +} diff --git a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyType.java b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyType.java new file mode 100644 index 0000000000000..2d2cc3b927240 --- /dev/null +++ b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyType.java @@ -0,0 +1,153 @@ +/* + * 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.spi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * One side of {@link BodyTypes}: the body a step needs or leaves. + *

+ * The carriers are coarse on purpose: a carrier that is right is better than a precise Java type that is wrong. Give + * the {@link #javaType()} only when the body is always of that type. + * + * @since 4.24 + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({}) +public @interface BodyType { + + /** + * The carriers of the body: the kinds of Java object it is in. Several for a body that can be any of them. None + * when the side is not known. + */ + Carrier[] value() default {}; + + /** + * The Java type of the body, when it is always of this type, such as + * {@code org.apache.camel.component.file.GenericFile}. + */ + String javaType() default ""; + + /** + * The format of the payload, when it is always the same: json, xml, csv, yaml, text or binary. These are the names + * of the data types of Kamelets. + */ + String format() default ""; + + /** + * The options the body depends on. The carriers hold while these options keep their default values; with another + * value the body is not known, unless {@link #byOption()} says it. + */ + String[] dependsOn() default {}; + + /** + * The body for other values of an option, such as {@code output=bytes}. + */ + When[] byOption() default {}; + + /** + * What to know about the body, in a sentence for the documentation and the tools. + */ + String description() default ""; + + /** + * The body when an option has a value. + */ + @Retention(RetentionPolicy.RUNTIME) + @Documented + @Target({}) + @interface When { + + /** + * The name of the option. + */ + String option(); + + /** + * The value of the option. + */ + String value(); + + /** + * The carriers of the body with this value. + */ + Carrier[] carriers(); + + /** + * The Java type of the body with this value, when it is always of this type. + */ + String javaType() default ""; + } + + /** + * The kinds of Java object a body is in. The runtime sorts the bodies it records into kinds as well (null, payload, + * data and collection): text, bytes, a stream, a file and a value are a payload, a map and a pojo are data, a list + * and an iterator are a collection. + */ + enum Carrier { + + /** No body. */ + NULL("null"), + /** Text: a String. */ + TEXT("text"), + /** A byte array. */ + BYTES("bytes"), + /** A stream: an InputStream, a Reader, or the stream cache of Camel. */ + STREAM("stream"), + /** A file: the GenericFile of the file consumers, a java.io.File or a Path. */ + FILE("file"), + /** A single value: a number, a boolean or an enum. */ + VALUE("value"), + /** A java.util.Map, such as a JSON object parsed or a row of a query. */ + MAP("map"), + /** A List, another Collection or an array, such as a JSON array parsed or the rows of a query. */ + LIST("list"), + /** + * An Iterator, which is read once, element by element, and has no size and no index: the rows of a query that + * streams them, or of a CSV file read lazily. + */ + ITERATOR("iterator"), + /** Another Java object, such as a DOM Document or a class a payload is unmarshalled into. */ + POJO("pojo"), + /** The body as it was before the step: for the body after a step only. */ + UNCHANGED("unchanged"), + /** Any body: for the bodies a step needs only. */ + ANY("any"), + /** + * The body depends on what the other system sends, or on a class the user configures: tooling must not guess + * it. + */ + UNKNOWN("unknown"); + + private final String value; + + Carrier(String value) { + this.value = value; + } + + /** + * The name of the carrier, as the catalog has it. + */ + public String getValue() { + return value; + } + } +} diff --git a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyTypes.java b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyTypes.java new file mode 100644 index 0000000000000..485ac6c17c7d8 --- /dev/null +++ b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/BodyTypes.java @@ -0,0 +1,105 @@ +/* + * 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.spi; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The message body a component, data format or language works with: the body it needs, and the body it leaves after it. + * The build carries it into the catalog, so tooling can say what the body is after a step without running the route: + * the validators, the catalog tools and the documentation. + *

+ * Put it on the endpoint class (the class with {@link UriEndpoint}), the data format class or the language class. A + * subclass does not have the body types of its superclass, as it may change the body: it declares its own. + *

+ * Each side that is given says what the body is with the default options. A side that is not given is not known, and + * tooling must not guess it. A side whose body depends on what the other system sends, or on a class the user + * configures, says {@link BodyType.Carrier#UNKNOWN}. + *

+ * For example the file component: the consumer gives the file it read, the producer writes text, bytes, a stream or a + * file, and leaves the body as it was: + * + *

+ * @BodyTypes(consumerOut = @BodyType(value = Carrier.FILE, description = "The file as read"),
+ *            producerIn = @BodyType({ Carrier.TEXT, Carrier.BYTES, Carrier.STREAM, Carrier.FILE }),
+ *            producerOut = @BodyType(Carrier.UNCHANGED))
+ * 
+ * + * @since 4.24 + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ ElementType.TYPE }) +public @interface BodyTypes { + + /** + * The body of the message the consumer creates. + */ + BodyType consumerOut() default @BodyType; + + /** + * The bodies a consumer that answers the caller can reply with: the body the route ends with, such as the response + * of an HTTP server. + */ + BodyType consumerReply() default @BodyType; + + /** + * The bodies the producer can send or write. + */ + BodyType producerIn() default @BodyType; + + /** + * The body after the producer: {@link BodyType.Carrier#UNCHANGED} when it leaves the body as it was, otherwise what + * it sets, such as the response of a call. + */ + BodyType producerOut() default @BodyType; + + /** + * The bodies a data format can marshal. + */ + BodyType marshalIn() default @BodyType; + + /** + * The body after marshal. + */ + BodyType marshalOut() default @BodyType; + + /** + * The bodies a data format can unmarshal. + */ + BodyType unmarshalIn() default @BodyType; + + /** + * The body after unmarshal. + */ + BodyType unmarshalOut() default @BodyType; + + /** + * The bodies the expressions of a language can read. + */ + BodyType expressionIn() default @BodyType; + + /** + * The result of an expression of a language, which becomes the body in setBody and transform. + */ + BodyType expressionOut() default @BodyType; + +}