diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml
index f6f06b4f00b66..88dca8855328f 100644
--- a/bom/camel-bom/pom.xml
+++ b/bom/camel-bom/pom.xml
@@ -2077,6 +2077,11 @@
camel-rest-openapi4.22.0-SNAPSHOT
+
+ org.apache.camel
+ camel-rest-postman
+ 4.22.0-SNAPSHOT
+ org.apache.camelcamel-robotframework
diff --git a/catalog/camel-allcomponents/pom.xml b/catalog/camel-allcomponents/pom.xml
index 0afdc8f168ada..678c6ba3a7ce5 100644
--- a/catalog/camel-allcomponents/pom.xml
+++ b/catalog/camel-allcomponents/pom.xml
@@ -1857,6 +1857,11 @@
camel-rest-openapi${project.version}
+
+ org.apache.camel
+ camel-rest-postman
+ ${project.version}
+ org.apache.camelcamel-robotframework
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components.properties
index 883f470b7e2ce..e8a80790f9c4c 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components.properties
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components.properties
@@ -314,6 +314,7 @@ ref
rest
rest-api
rest-openapi
+rest-postman
robotframework
rocketmq
rss
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-postman.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-postman.json
new file mode 100644
index 0000000000000..8b2c3ae3d0686
--- /dev/null
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/rest-postman.json
@@ -0,0 +1,101 @@
+{
+ "component": {
+ "kind": "component",
+ "name": "rest-postman",
+ "title": "REST Postman",
+ "description": "To call and expose REST services using a Postman Collection as contract.",
+ "deprecated": false,
+ "firstVersion": "4.22.0",
+ "label": "rest,api",
+ "javaType": "org.apache.camel.component.rest.postman.RestPostmanComponent",
+ "supportLevel": "Preview",
+ "groupId": "org.apache.camel",
+ "artifactId": "camel-rest-postman",
+ "version": "4.22.0-SNAPSHOT",
+ "scheme": "rest-postman",
+ "extendsScheme": "",
+ "syntax": "rest-postman:collectionSource#requestId",
+ "async": false,
+ "api": false,
+ "consumerOnly": false,
+ "producerOnly": false,
+ "lenientProperties": false,
+ "browsable": false,
+ "remote": true
+ },
+ "componentProperties": {
+ "basePath": { "index": 0, "kind": "property", "displayName": "Base Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "API basePath, for example \/v2. Default is unset, if set overrides the value derived from the request URL in the collection." },
+ "collectionSource": { "index": 1, "kind": "property", "displayName": "Collection Source", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The Postman Collection to use, when it is not given on the endpoint. Either a resource URI of a Collection v2.1 JSON document (classpath:, file: or http:), or the uid of a collection to fetch from the Postman cloud." },
+ "collectionSourceType": { "index": 2, "kind": "property", "displayName": "Collection Source Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "auto", "resource", "cloud" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "auto", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How to interpret collectionSource. With auto, a bare collection UUID or {ownerId}-{uuid} is fetched from the Postman cloud and anything else is resolved as a resource (classpath:, file:, http:). Use resource or cloud to decide explicitly." },
+ "variables": { "index": 3, "kind": "property", "displayName": "Variables", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "variable.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Values for the {{variable}} placeholders used in the collection. These override the variables declared by the collection and its folders. This is a multi-value option with prefix: variable." },
+ "failOnUnresolvedVariable": { "index": 4, "kind": "property", "displayName": "Fail On Unresolved Variable", "group": "common (advanced)", "label": "common,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether to fail if a {{variable}} placeholder used by the selected request cannot be resolved. When false the placeholder is left as-is." },
+ "apiContextPath": { "index": 5, "kind": "property", "displayName": "Api Context Path", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Sets the context-path to use for servicing the Postman collection document. The document is served with all auth blocks and all secret variables removed." },
+ "bridgeErrorHandler": { "index": 6, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "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." },
+ "clientRequestValidation": { "index": 7, "kind": "property", "displayName": "Client Request Validation", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether to enable validation of the client request. A Postman collection has no schemas, so this is a best-effort check of required headers, query parameters and body presence only." },
+ "missingRequest": { "index": 8, "kind": "property", "displayName": "Missing Request", "group": "consumer", "label": "consumer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "fail", "ignore", "mock" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "fail", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether the consumer should fail, ignore or return a mock response for requests in the collection that are not mapped to a corresponding route." },
+ "consumerComponentName": { "index": 9, "kind": "property", "displayName": "Consumer Component Name", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Name of the Camel component that will service the requests. The component must be present in Camel registry and it must be able to service contract-first REST consumers, as platform-http does. If not set CLASSPATH is searched for a single component with that capability." },
+ "mockIncludePattern": { "index": 10, "kind": "property", "displayName": "Mock Include Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "classpath:camel-mock\/**", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Used for inclusive filtering of mock data from directories. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. Saved example responses in the collection are preferred over these files." },
+ "requestFilter": { "index": 11, "kind": "property", "displayName": "Request Filter", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Filters which requests of the collection are used, as comma separated Ant-style patterns matched against the folder qualified request id. Prefix a pattern with ! to exclude." },
+ "restPostmanProcessorStrategy": { "index": 12, "kind": "property", "displayName": "Rest Postman Processor Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.rest.postman.RestPostmanProcessorStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom strategy for how to service the requests of the collection." },
+ "host": { "index": 13, "kind": "property", "displayName": "Host", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Scheme hostname and port to direct the HTTP requests to in the form of https:\/\/hostname:port. If set overrides any value derived from the collection." },
+ "lazyStartProducer": { "index": 14, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." },
+ "runFailFast": { "index": 15, "kind": "property", "displayName": "Run Fail Fast", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "When the endpoint runs more than one request, that is when it selects a folder or the whole collection, whether to stop and fail on the first request that fails. When false every request is attempted and the failure is recorded in its result." },
+ "componentName": { "index": 16, "kind": "property", "displayName": "Component Name", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Name of the Camel component that will perform the requests. The component must be present in Camel registry and it must implement RestProducerFactory service provider interface. If not set CLASSPATH is searched for single component that implements RestProducerFactory SPI." },
+ "consumes": { "index": 17, "kind": "property", "displayName": "Consumes", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What payload type this component is capable of consuming. This equates to the value of the Accept HTTP header. A Postman collection does not describe responses, so unlike an OpenAPI specification there is nothing to infer this from and it is unset by default." },
+ "produces": { "index": 18, "kind": "property", "displayName": "Produces", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What payload type this component is producing. This equates to the value of the Content-Type HTTP header. If not set it is inferred from the body mode of the request in the collection." },
+ "queryParameterMode": { "index": 19, "kind": "property", "displayName": "Query Parameter Mode", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "placeholder", "literal" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "placeholder", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How to treat the query parameters declared in the collection. With placeholder the parameter names are bound to message headers and the values in the collection are ignored as sample data. With literal the values in the collection are sent as-is." },
+ "autowiredEnabled": { "index": 20, "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." },
+ "collectionCacheTtl": { "index": 21, "kind": "property", "displayName": "Collection Cache Ttl", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": -1, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How long a loaded collection is cached, in milliseconds. Use -1 to cache for the lifetime of the component." },
+ "configuration": { "index": 22, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The shared configuration used as the template for every endpoint created by this component." },
+ "connectTimeout": { "index": 23, "kind": "property", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 15000, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Connection timeout in milliseconds when fetching a collection from the Postman cloud." },
+ "requestTimeout": { "index": 24, "kind": "property", "displayName": "Request Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds when fetching a collection from the Postman cloud." },
+ "collectionAuth": { "index": 25, "kind": "property", "displayName": "Collection Auth", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ignore", "header", "fail" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "ignore", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What to do with the auth block the collection declares for the target API. With ignore the block is not applied, and a warning names the type that was found. With header the basic, bearer and apikey types are applied as a static header or query parameter, and any other type fails at startup rather than silently sending no credential. With fail any auth block other than noauth is rejected." },
+ "oauthProfile": { "index": 26, "kind": "property", "displayName": "OAuth Profile", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The OAuth profile to use for authenticating the incoming requests. The profile is enforced by the consumer component servicing the requests." },
+ "postmanApiKey": { "index": 27, "kind": "property", "displayName": "Postman Api Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The Postman API key used to fetch the collection from the Postman cloud. This credential authenticates against Postman itself and is never sent to the API the collection describes." },
+ "postmanApiKeyHeader": { "index": 28, "kind": "property", "displayName": "Postman Api Key Header", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "X-Api-Key", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The HTTP header used to send the Postman API key when fetching a collection." },
+ "sslContextParameters": { "index": 29, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Customize TLS parameters used by the component. If not set defaults to the TLS parameters set in the Camel context. These parameters are used both when fetching a collection from the Postman cloud and by the delegate producer." },
+ "useGlobalSslContextParameters": { "index": 30, "kind": "property", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Enable usage of global SSL context parameters." },
+ "postmanApiUrl": { "index": 31, "kind": "property", "displayName": "Postman Api Url", "group": "security (advanced)", "label": "security,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "https:\/\/api.getpostman.com", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The base URL of the Postman API used to fetch collections. Must use https, except for localhost, because plain http would send the Postman API key in clear text." }
+ },
+ "headers": {
+ "CamelRestPostmanRequestId": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The id of the Postman request being invoked or serviced.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#REQUEST_ID" },
+ "CamelRestPostmanRequestName": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Postman request, as written in the collection.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#REQUEST_NAME" },
+ "CamelRestPostmanFolderPath": { "index": 2, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The folder path of the Postman request, with folders separated by a slash.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#FOLDER_PATH" },
+ "CamelRestPostmanRequestCount": { "index": 3, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of requests executed when running a folder or a whole collection.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#REQUEST_COUNT" },
+ "CamelRestPostmanFailedCount": { "index": 4, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of requests that failed when running a folder or a whole collection with runFailFast disabled.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#FAILED_COUNT" }
+ },
+ "properties": {
+ "collectionSource": { "index": 0, "kind": "path", "displayName": "Collection Source", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "postman-collection.json", "description": "The Postman Collection to use. Either a resource URI of a Collection v2.1 JSON document (classpath:, file: or http:), or the uid of a collection to fetch from the Postman cloud, which requires postmanApiKey. Default value notice: By default loads the postman-collection.json file" },
+ "requestId": { "index": 1, "kind": "path", "displayName": "Request Id", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The request to invoke, identified by its id in the collection or by its slugified name, for example getUserById. Use a folder id to run every request in that folder, and leave it out to run the whole collection. Append a slash to force a folder match when a request and a folder share a name." },
+ "basePath": { "index": 2, "kind": "parameter", "displayName": "Base Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "API basePath, for example \/v2. Default is unset, if set overrides the value derived from the request URL in the collection." },
+ "collectionSourceType": { "index": 3, "kind": "parameter", "displayName": "Collection Source Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "auto", "resource", "cloud" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "auto", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How to interpret collectionSource. With auto, a bare collection UUID or {ownerId}-{uuid} is fetched from the Postman cloud and anything else is resolved as a resource (classpath:, file:, http:). Use resource or cloud to decide explicitly." },
+ "variables": { "index": 4, "kind": "parameter", "displayName": "Variables", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "variable.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Values for the {{variable}} placeholders used in the collection. These override the variables declared by the collection and its folders. This is a multi-value option with prefix: variable." },
+ "failOnUnresolvedVariable": { "index": 5, "kind": "parameter", "displayName": "Fail On Unresolved Variable", "group": "common (advanced)", "label": "common,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether to fail if a {{variable}} placeholder used by the selected request cannot be resolved. When false the placeholder is left as-is." },
+ "apiContextPath": { "index": 6, "kind": "parameter", "displayName": "Api Context Path", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Sets the context-path to use for servicing the Postman collection document. The document is served with all auth blocks and all secret variables removed." },
+ "clientRequestValidation": { "index": 7, "kind": "parameter", "displayName": "Client Request Validation", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether to enable validation of the client request. A Postman collection has no schemas, so this is a best-effort check of required headers, query parameters and body presence only." },
+ "missingRequest": { "index": 8, "kind": "parameter", "displayName": "Missing Request", "group": "consumer", "label": "consumer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "fail", "ignore", "mock" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "fail", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether the consumer should fail, ignore or return a mock response for requests in the collection that are not mapped to a corresponding route." },
+ "bridgeErrorHandler": { "index": 9, "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." },
+ "consumerComponentName": { "index": 10, "kind": "parameter", "displayName": "Consumer Component Name", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Name of the Camel component that will service the requests. The component must be present in Camel registry and it must be able to service contract-first REST consumers, as platform-http does. If not set CLASSPATH is searched for a single component with that capability." },
+ "exceptionHandler": { "index": 11, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored." },
+ "exchangePattern": { "index": 12, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." },
+ "mockIncludePattern": { "index": 13, "kind": "parameter", "displayName": "Mock Include Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "classpath:camel-mock\/**", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Used for inclusive filtering of mock data from directories. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. Saved example responses in the collection are preferred over these files." },
+ "requestFilter": { "index": 14, "kind": "parameter", "displayName": "Request Filter", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Filters which requests of the collection are used, as comma separated Ant-style patterns matched against the folder qualified request id. Prefix a pattern with ! to exclude." },
+ "host": { "index": 15, "kind": "parameter", "displayName": "Host", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Scheme hostname and port to direct the HTTP requests to in the form of https:\/\/hostname:port. If set overrides any value derived from the collection." },
+ "runFailFast": { "index": 16, "kind": "parameter", "displayName": "Run Fail Fast", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "When the endpoint runs more than one request, that is when it selects a folder or the whole collection, whether to stop and fail on the first request that fails. When false every request is attempted and the failure is recorded in its result." },
+ "componentName": { "index": 17, "kind": "parameter", "displayName": "Component Name", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Name of the Camel component that will perform the requests. The component must be present in Camel registry and it must implement RestProducerFactory service provider interface. If not set CLASSPATH is searched for single component that implements RestProducerFactory SPI." },
+ "consumes": { "index": 18, "kind": "parameter", "displayName": "Consumes", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What payload type this component is capable of consuming. This equates to the value of the Accept HTTP header. A Postman collection does not describe responses, so unlike an OpenAPI specification there is nothing to infer this from and it is unset by default." },
+ "lazyStartProducer": { "index": 19, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." },
+ "produces": { "index": 20, "kind": "parameter", "displayName": "Produces", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What payload type this component is producing. This equates to the value of the Content-Type HTTP header. If not set it is inferred from the body mode of the request in the collection." },
+ "queryParameterMode": { "index": 21, "kind": "parameter", "displayName": "Query Parameter Mode", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "placeholder", "literal" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "placeholder", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How to treat the query parameters declared in the collection. With placeholder the parameter names are bound to message headers and the values in the collection are ignored as sample data. With literal the values in the collection are sent as-is." },
+ "collectionCacheTtl": { "index": 22, "kind": "parameter", "displayName": "Collection Cache Ttl", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": -1, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How long a loaded collection is cached, in milliseconds. Use -1 to cache for the lifetime of the component." },
+ "connectTimeout": { "index": 23, "kind": "parameter", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 15000, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Connection timeout in milliseconds when fetching a collection from the Postman cloud." },
+ "requestTimeout": { "index": 24, "kind": "parameter", "displayName": "Request Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds when fetching a collection from the Postman cloud." },
+ "collectionAuth": { "index": 25, "kind": "parameter", "displayName": "Collection Auth", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ignore", "header", "fail" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "ignore", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What to do with the auth block the collection declares for the target API. With ignore the block is not applied, and a warning names the type that was found. With header the basic, bearer and apikey types are applied as a static header or query parameter, and any other type fails at startup rather than silently sending no credential. With fail any auth block other than noauth is rejected." },
+ "oauthProfile": { "index": 26, "kind": "parameter", "displayName": "OAuth Profile", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The OAuth profile to use for authenticating the incoming requests. The profile is enforced by the consumer component servicing the requests." },
+ "postmanApiKey": { "index": 27, "kind": "parameter", "displayName": "Postman Api Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The Postman API key used to fetch the collection from the Postman cloud. This credential authenticates against Postman itself and is never sent to the API the collection describes." },
+ "postmanApiKeyHeader": { "index": 28, "kind": "parameter", "displayName": "Postman Api Key Header", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "X-Api-Key", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The HTTP header used to send the Postman API key when fetching a collection." },
+ "sslContextParameters": { "index": 29, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Customize TLS parameters used by the component. If not set defaults to the TLS parameters set in the Camel context. These parameters are used both when fetching a collection from the Postman cloud and by the delegate producer." },
+ "useGlobalSslContextParameters": { "index": 30, "kind": "parameter", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Enable usage of global SSL context parameters." },
+ "postmanApiUrl": { "index": 31, "kind": "parameter", "displayName": "Postman Api Url", "group": "security (advanced)", "label": "security,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "https:\/\/api.getpostman.com", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The base URL of the Postman API used to fetch collections. Must use https, except for localhost, because plain http would send the Postman API key in clear text." }
+ }
+}
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties
index 36ae1f24a7418..8d546b6b2a1e7 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs.properties
@@ -527,6 +527,7 @@ resourceresolver-github
rest-api-component
rest-component
rest-openapi-component
+rest-postman-component
resumable-eip
resume-strategies
return-address
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/rest-postman-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/rest-postman-component.adoc
new file mode 100644
index 0000000000000..43513d2229ce3
--- /dev/null
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/rest-postman-component.adoc
@@ -0,0 +1,232 @@
+= REST Postman Component
+:doctitle: REST Postman
+:shortname: rest-postman
+:artifactid: camel-rest-postman
+:description: To call and expose REST services using a Postman Collection as contract.
+:since: 4.22
+:supportlevel: Preview
+:tabs-sync-option:
+:component-header: Both producer and consumer are supported
+
+*Since Camel {since}*
+
+*{component-header}*
+
+The REST Postman component configures rest producers and contract-first rest consumers from a
+https://learning.postman.com/docs/collections/collections-overview/[Postman Collection], and delegates to a component
+implementing the _RestProducerFactory_ interface. Currently, known working components are:
+
+* xref:http-component.adoc[http]
+* xref:netty-http-component.adoc[netty-http]
+* xref:undertow-component.adoc[undertow]
+* xref:vertx-http-component.adoc[vertx-http]
+
+It is the Postman equivalent of xref:rest-openapi-component.adoc[rest-openapi]. Use it when a Postman Collection is
+the description of the API you have, rather than an OpenAPI specification.
+
+IMPORTANT: Only the Postman Collection Format v2.1 is supported.
+
+Maven users will need to add the following dependency to their `pom.xml` for this component:
+
+[source,xml]
+----
+
+ org.apache.camel
+ camel-rest-postman
+ x.x.x
+
+
+----
+
+== URI format
+
+----
+rest-postman:[collectionSource#]requestId
+----
+
+`collectionSource` is either a resource URI of a Collection v2.1 JSON document (`classpath:`, `file:` or `http:`), or
+the uid of a collection to fetch from the Postman cloud. It defaults to `postman-collection.json` on the classpath.
+
+`requestId` selects what to invoke:
+
+[cols="1,3", options="header"]
+|===
+| Fragment | Selects
+| `getPetById` | the single request whose name slugifies to `getPetById`
+| `pets/addPet` | the request `Add Pet` inside the folder `Pets`, used when a name is not unique
+| `3f2504e0-4f89-11d3-9a0c-0305e82c3301` | the request with that `id`, which only collections fetched from the cloud carry
+| `pets` | every request in the folder `Pets`
+| `pets/` | the folder `Pets`, forced, for when a request and a folder share a name
+| _omitted_ | every request in the collection
+|===
+
+This component's endpoint URI is lenient, which means that in addition to message headers you can specify a request's
+parameters as endpoint parameters. These will be constant for all subsequent invocations, so it makes sense to use
+this feature only for parameters that are indeed constant for all invocations.
+
+// component options: START
+include::partial$component-configure-options.adoc[]
+include::partial$component-endpoint-options.adoc[]
+include::partial$component-endpoint-headers.adoc[]
+// component options: END
+
+== Usage
+
+=== Identifying requests
+
+A Postman item has a human readable name rather than an operation id, so this component slugifies it:
+`Get Pet By Id` becomes `getPetById`. When two requests slugify to the same thing, both are addressed by their folder
+qualified id instead, such as `pets/get` and `users/get`, and using the bare `get` is an error that lists the
+alternatives.
+
+`item.id` is also accepted when the collection records one. Note that it is optional in the v2.1 schema, and Postman's
+exporter strips auto-generated item ids, so an exported `collection.json` usually has none. Collections fetched from
+the Postman cloud do.
+
+IMPORTANT: Because the common case is to address a request by its slugified name, renaming a request in the Postman
+UI changes its id and will break routes bound to it.
+
+=== Invoking a single request
+
+[source,java]
+----
+from("direct:start")
+ .to("rest-postman:petstore.json#getPetById");
+----
+
+The message body and headers of the exchange are what is sent. The collection supplies the method, the URL, and any
+headers the message does not already carry; the body written in the collection is treated as sample data and is not
+sent.
+
+Path parameters written as `:petId` become `+{petId}+` placeholders resolved per exchange from the message header of the
+same name, falling back to the value declared in `url.variable`. Query parameters are bound to message headers in the
+same way and are dropped when unresolved. Set `queryParameterMode=literal` to send the values written in the
+collection instead.
+
+=== Running a folder or a whole collection
+
+Naming a folder, or naming nothing at all, runs every request in turn, in the manner of Postman's collection runner:
+
+[source,java]
+----
+from("direct:smokeTest")
+ .to("rest-postman:petstore.json#pets") // every request in the Pets folder
+ .to("rest-postman:petstore.json"); // every request in the collection
+----
+
+Because one exchange body cannot stand in for many different requests, each request sends the body and headers written
+in the collection. `raw`, `graphql` and `urlencoded` bodies are reconstructed; `formdata` and `file` bodies cannot be,
+and are skipped with a warning. The `file` body mode is never read from disk, as it records a path on the machine of
+whoever authored the collection.
+
+The message body becomes a `List` of `PostmanRunResult`, one per request, each carrying the request id, method, URI,
+status code, response body, headers and any failure. The headers `CamelRestPostmanRequestCount` and
+`CamelRestPostmanFailedCount` summarise the run.
+
+By default the run stops and fails on the first request that fails. Set `runFailFast=false` to attempt every request
+and record the failures in their results instead:
+
+[source,java]
+----
+from("timer:smoke?period=60000")
+ .to("rest-postman:petstore.json?runFailFast=false")
+ .split(body())
+ .filter(simple("${body.success} == false"))
+ .to("log:failures");
+----
+
+=== Contract-first consumer
+
+Pointing a route's `from` at a collection serves its requests over HTTP, dispatching each one to a route consuming
+from `direct:`:
+
+[source,java]
+----
+from("rest-postman:petstore.json")
+ .to("direct:dummy");
+
+from("direct:getPetById")
+ .setBody(constant("{ \"id\": 42 }"));
+----
+
+Use a folder id to serve only part of a collection, and `requestFilter` to include or exclude requests by Ant-style
+patterns over their folder qualified ids.
+
+Because a collection does not describe a base path, the split between base path and route path is inferred from what
+`+{{baseUrl}}+` expands to. Set `basePath` explicitly to control the context path the consumer serves on.
+
+`missingRequest` decides what happens when a request has no corresponding route: `fail` (the default) refuses to
+start, `ignore` warns, and `mock` returns a mocked response. Mock responses are taken from the collection's own saved
+example responses where there are any, which is the one place a collection is richer than an OpenAPI specification,
+and fall back to files matched by `mockIncludePattern`.
+
+If two requests share an HTTP method and path, which is common when a collection keeps a success and an error variant
+of the same call, the consumer fails at startup rather than letting one silently shadow the other. Use `requestFilter`
+to choose between them.
+
+=== Variables
+
+`+{{variable}}+` placeholders are resolved from the collection's own `variable` arrays, with folder scopes overriding
+the collection scope, then from the endpoint's `variables` option, then from Camel property placeholders:
+
+[source,java]
+----
+from("direct:start")
+ .to("rest-postman:petstore.json#getPetById?variable.baseUrl=https://staging.example.com/v3");
+----
+
+Postman environment files are not supported. Unresolved placeholders are left as they are unless
+`failOnUnresolvedVariable=true`.
+
+A placeholder name written in the `prefix:value` form of a Camel property placeholder function -- `+{{env:HOME}}+`,
+`+{{sys:user.home}}+`, `+{{bean:foo}}+` and the vault functions among them -- is deliberately *not* resolved from
+Camel properties. A collection is route-author configuration, but a cloud-hosted one is editable by anyone with
+access to the Postman workspace, and resolving those would let its content pull an environment variable into an
+outgoing request. Supply such values through the `variables` option instead.
+
+NOTE: Pre-request and test scripts in the collection's `event` blocks are never parsed or executed.
+
+== Security
+
+=== Two different credentials
+
+There are two unrelated credentials in play, and the option names keep them apart:
+
+`postmanApiKey`:: authenticates against *Postman itself*, in order to download a collection from the Postman cloud. It
+is sent only to `postmanApiUrl`, and never to the API that the collection describes.
+
+the collection's own `auth` block:: authenticates against *the API the collection describes*. It is governed by the
+`collectionAuth` option.
+
+=== Fetching a collection from the Postman cloud
+
+[source,java]
+----
+from("direct:start")
+ .to("rest-postman:12ece9e1-2abf-4edc-8e34-de66e74114d2#getPetById?postmanApiKey=PMAK-xxxx");
+----
+
+Prefer resolving the key from a vault or a property placeholder over writing it in the URI. Redirects from
+`postmanApiUrl` are rejected rather than followed, because following one would send the key to the redirect target,
+and `postmanApiUrl` must use HTTPS unless it names a loopback host.
+
+=== Applying the collection's auth block
+
+`collectionAuth` defaults to `ignore`: the block is not applied, and a warning names the type that was found. This is
+deliberate, because the values in a collection's auth block are usually unresolved `+{{placeholders}}+`, and silently
+attaching a credential found in a configuration file to outbound requests is surprising.
+
+Set `collectionAuth=header` to apply it. The `basic`, `bearer` and `apikey` types are reproduced as a static header or
+query parameter. The types that require per-request signing or a token exchange -- `awsv4`, `digest`, `hawk`,
+`edgegrid`, `ntlm`, `oauth1` and `oauth2` -- fail at startup rather than silently sending no credential; configure
+those on the delegate HTTP component instead. `collectionAuth=fail` rejects any auth block at all.
+
+On the consumer side the collection's auth block describes what a client must present, and is *not* enforced. Use
+`oauthProfile`, or the delegate consumer component's own authentication, for that.
+
+=== Serving the collection document
+
+When `apiContextPath` is set, the collection is served on that path with every `auth` block removed and the value of
+every variable of type `secret` replaced. This redaction is unconditional.
+
+include::spring-boot:partial$starter.adoc[]
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/sensitive-keys.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/sensitive-keys.json
index 376fbe12a04b7..5e570545f70c1 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/sensitive-keys.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/sensitive-keys.json
@@ -49,6 +49,7 @@
"passphrase",
"password",
"personalaccesstoken",
+ "postmanapikey",
"privatekey",
"privatekeyfile",
"privatekeyname",
diff --git a/components/camel-platform-http-vertx/pom.xml b/components/camel-platform-http-vertx/pom.xml
index c50726b4b1cf3..870f9b48ab83a 100644
--- a/components/camel-platform-http-vertx/pom.xml
+++ b/components/camel-platform-http-vertx/pom.xml
@@ -99,6 +99,11 @@
camel-rest-openapitest
+
+ org.apache.camel
+ camel-rest-postman
+ test
+ org.apache.camelcamel-vertx-http
diff --git a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestPostmanConsumerTest.java b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestPostmanConsumerTest.java
new file mode 100644
index 0000000000000..ae2f889441b53
--- /dev/null
+++ b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestPostmanConsumerTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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.component.platform.http.vertx;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.hamcrest.Matchers.equalTo;
+
+/**
+ * Contract-first consumer driven by a Postman Collection.
+ */
+class PlatformHttpRestPostmanConsumerTest {
+
+ @Test
+ void shouldServeARequestOfTheCollection() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-postman:classpath:postman-petstore.json?missingRequest=ignore")
+ .to("mock:result");
+
+ from("direct:getPetById")
+ .setBody().constant("{\"pet\": \"tony the tiger\"}");
+ }
+ });
+
+ VertxPlatformHttpEngineTest.startCamelContext(context);
+
+ MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class);
+ mock.expectedMessageCount(1);
+
+ given()
+ .when()
+ .get("/api/v3/pet/123")
+ .then()
+ .statusCode(200)
+ .body(equalTo("{\"pet\": \"tony the tiger\"}"));
+
+ mock.assertIsSatisfied();
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ void shouldMapPathParametersToHeaders() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-postman:classpath:postman-petstore.json?missingRequest=ignore").stop();
+
+ from("direct:getPetById")
+ .setBody().simple("pet=${header.petId}");
+ }
+ });
+
+ VertxPlatformHttpEngineTest.startCamelContext(context);
+
+ given()
+ .when()
+ .get("/api/v3/pet/123")
+ .then()
+ .statusCode(200)
+ .body(equalTo("pet=123"));
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ void shouldReturn404ForAPathTheCollectionDoesNotDescribe() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-postman:classpath:postman-petstore.json?missingRequest=ignore").stop();
+
+ from("direct:getPetById").setBody().constant("ok");
+ }
+ });
+
+ VertxPlatformHttpEngineTest.startCamelContext(context);
+
+ given()
+ .when()
+ .get("/api/v3/unknown")
+ .then()
+ .statusCode(404);
+ } finally {
+ context.stop();
+ }
+ }
+
+ /**
+ * Note that for a path the collection does describe, the 405 is produced by the vert.x router itself, because the
+ * path was registered with only the verbs the collection uses. The router does not populate {@code Allow}, so this
+ * asserts the status only.
+ */
+ @Test
+ void shouldReturn405ForAKnownPathOnTheWrongMethod() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-postman:classpath:postman-petstore.json?missingRequest=ignore").stop();
+
+ from("direct:getPetById").setBody().constant("ok");
+ }
+ });
+
+ VertxPlatformHttpEngineTest.startCamelContext(context);
+
+ given()
+ .when()
+ .delete("/api/v3/pet")
+ .then()
+ .statusCode(405);
+ } finally {
+ context.stop();
+ }
+ }
+
+ /**
+ * The default refuses to start rather than silently serving a collection whose requests go nowhere.
+ */
+ @Test
+ void shouldFailToStartWhenARequestHasNoRoute() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-postman:classpath:postman-petstore.json").stop();
+ }
+ });
+
+ assertThatThrownBy(() -> VertxPlatformHttpEngineTest.startCamelContext(context))
+ .rootCause()
+ .hasMessageContaining("not mapped to a corresponding route")
+ .hasMessageContaining("direct:getPetById");
+ } finally {
+ context.stop();
+ }
+ }
+
+ /**
+ * Saved example responses in the collection are real recorded responses, so they make better mocks than anything
+ * that could be generated.
+ */
+ @Test
+ void shouldMockFromASavedExampleResponse() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-postman:classpath:postman-petstore.json?missingRequest=mock").stop();
+ }
+ });
+
+ VertxPlatformHttpEngineTest.startCamelContext(context);
+
+ given()
+ .when()
+ .get("/api/v3/pet/123")
+ .then()
+ .statusCode(200)
+ .body(equalTo("{\"pet\": \"mocked tiger\"}"));
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ void shouldServeTheCollectionWithCredentialsRedacted() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-postman:classpath:postman-petstore.json"
+ + "?missingRequest=ignore&apiContextPath=/collection").stop();
+
+ from("direct:getPetById").setBody().constant("ok");
+ }
+ });
+
+ VertxPlatformHttpEngineTest.startCamelContext(context);
+
+ String document = given()
+ .when()
+ .get("/api/v3/collection")
+ .then()
+ .statusCode(200)
+ .extract().body().asString();
+
+ assertThat(document).contains("Petstore").contains("Get Pet By Id");
+ // the bearer token and the secret variable must not be published
+ assertThat(document).doesNotContain("s3cr3t").doesNotContain("\"auth\"");
+ } finally {
+ context.stop();
+ }
+ }
+
+ @Test
+ void shouldServeOnlyTheRequestsSelectedByTheFilter() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-postman:classpath:postman-petstore.json?requestFilter=getPetById").stop();
+
+ from("direct:getPetById").setBody().constant("ok");
+ }
+ });
+
+ // starting proves validation passed even though addPet has no route, because it was filtered out
+ VertxPlatformHttpEngineTest.startCamelContext(context);
+
+ given().when().get("/api/v3/pet/123").then().statusCode(200).body(equalTo("ok"));
+ given().when().post("/api/v3/pet").then().statusCode(404);
+ } finally {
+ context.stop();
+ }
+ }
+}
diff --git a/components/camel-platform-http-vertx/src/test/resources/postman-petstore.json b/components/camel-platform-http-vertx/src/test/resources/postman-petstore.json
new file mode 100644
index 0000000000000..005d236178c14
--- /dev/null
+++ b/components/camel-platform-http-vertx/src/test/resources/postman-petstore.json
@@ -0,0 +1,88 @@
+{
+ "info": {
+ "_postman_id": "9c6b0f52-4c1e-4a3a-9e57-3a2b4a1f9c22",
+ "name": "Petstore",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "variable": [
+ {
+ "key": "baseUrl",
+ "value": "https://api.example.com/api/v3"
+ },
+ {
+ "key": "apiToken",
+ "value": "s3cr3t",
+ "type": "secret"
+ }
+ ],
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{apiToken}}"
+ }
+ ]
+ },
+ "item": [
+ {
+ "name": "Get Pet By Id",
+ "request": {
+ "method": "GET",
+ "url": {
+ "raw": "{{baseUrl}}/pet/:petId",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "pet",
+ ":petId"
+ ]
+ }
+ },
+ "response": [
+ {
+ "name": "found",
+ "code": 200,
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": "{\"pet\": \"mocked tiger\"}"
+ }
+ ]
+ },
+ {
+ "name": "Add Pet",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\"name\":\"Rex\"}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/pet",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "pet"
+ ]
+ }
+ }
+ }
+ ]
+}
diff --git a/components/camel-rest-postman/pom.xml b/components/camel-rest-postman/pom.xml
new file mode 100644
index 0000000000000..ce8b1af53c4f5
--- /dev/null
+++ b/components/camel-rest-postman/pom.xml
@@ -0,0 +1,96 @@
+
+
+
+
+ 4.0.0
+
+
+ org.apache.camel
+ components
+ 4.22.0-SNAPSHOT
+
+
+ camel-rest-postman
+
+ Camel :: REST Postman
+ Camel REST support using Postman Collections
+
+
+ 4.22.0
+
+
+
+
+
+
+ org.apache.camel
+ camel-support
+
+
+
+ org.apache.camel
+ camel-rest
+
+
+ org.apache.camel
+ camel-platform-http
+
+
+
+
+ org.apache.camel
+ camel-test-junit6
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+ org.wiremock
+ wiremock
+ ${wiremock-version}
+ test
+
+
+
+ org.apache.camel
+ camel-http
+ test
+
+
+
+ org.apache.camel
+ camel-vertx-http
+ test
+
+
+
+
+
diff --git a/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanComponentConfigurer.java b/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanComponentConfigurer.java
new file mode 100644
index 0000000000000..008d1da49dfa8
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanComponentConfigurer.java
@@ -0,0 +1,237 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.rest.postman;
+
+import javax.annotation.processing.Generated;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.support.component.PropertyConfigurerSupport;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo")
+@SuppressWarnings("unchecked")
+public class RestPostmanComponentConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+ private org.apache.camel.component.rest.postman.RestPostmanConfiguration getOrCreateConfiguration(RestPostmanComponent target) {
+ if (target.getConfiguration() == null) {
+ target.setConfiguration(new org.apache.camel.component.rest.postman.RestPostmanConfiguration());
+ }
+ return target.getConfiguration();
+ }
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) {
+ RestPostmanComponent target = (RestPostmanComponent) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": getOrCreateConfiguration(target).setApiContextPath(property(camelContext, java.lang.String.class, value)); return true;
+ case "autowiredenabled":
+ case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true;
+ case "basepath":
+ case "basePath": getOrCreateConfiguration(target).setBasePath(property(camelContext, java.lang.String.class, value)); return true;
+ case "bridgeerrorhandler":
+ case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true;
+ case "clientrequestvalidation":
+ case "clientRequestValidation": getOrCreateConfiguration(target).setClientRequestValidation(property(camelContext, boolean.class, value)); return true;
+ case "collectionauth":
+ case "collectionAuth": getOrCreateConfiguration(target).setCollectionAuth(property(camelContext, java.lang.String.class, value)); return true;
+ case "collectioncachettl":
+ case "collectionCacheTtl": getOrCreateConfiguration(target).setCollectionCacheTtl(property(camelContext, long.class, value)); return true;
+ case "collectionsource":
+ case "collectionSource": target.setCollectionSource(property(camelContext, java.lang.String.class, value)); return true;
+ case "collectionsourcetype":
+ case "collectionSourceType": getOrCreateConfiguration(target).setCollectionSourceType(property(camelContext, java.lang.String.class, value)); return true;
+ case "componentname":
+ case "componentName": getOrCreateConfiguration(target).setComponentName(property(camelContext, java.lang.String.class, value)); return true;
+ case "configuration": target.setConfiguration(property(camelContext, org.apache.camel.component.rest.postman.RestPostmanConfiguration.class, value)); return true;
+ case "connecttimeout":
+ case "connectTimeout": getOrCreateConfiguration(target).setConnectTimeout(property(camelContext, long.class, value)); return true;
+ case "consumercomponentname":
+ case "consumerComponentName": getOrCreateConfiguration(target).setConsumerComponentName(property(camelContext, java.lang.String.class, value)); return true;
+ case "consumes": getOrCreateConfiguration(target).setConsumes(property(camelContext, java.lang.String.class, value)); return true;
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": getOrCreateConfiguration(target).setFailOnUnresolvedVariable(property(camelContext, boolean.class, value)); return true;
+ case "host": getOrCreateConfiguration(target).setHost(property(camelContext, java.lang.String.class, value)); return true;
+ case "lazystartproducer":
+ case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true;
+ case "missingrequest":
+ case "missingRequest": getOrCreateConfiguration(target).setMissingRequest(property(camelContext, java.lang.String.class, value)); return true;
+ case "mockincludepattern":
+ case "mockIncludePattern": getOrCreateConfiguration(target).setMockIncludePattern(property(camelContext, java.lang.String.class, value)); return true;
+ case "oauthprofile":
+ case "oauthProfile": getOrCreateConfiguration(target).setOauthProfile(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapikey":
+ case "postmanApiKey": getOrCreateConfiguration(target).setPostmanApiKey(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": getOrCreateConfiguration(target).setPostmanApiKeyHeader(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapiurl":
+ case "postmanApiUrl": getOrCreateConfiguration(target).setPostmanApiUrl(property(camelContext, java.lang.String.class, value)); return true;
+ case "produces": getOrCreateConfiguration(target).setProduces(property(camelContext, java.lang.String.class, value)); return true;
+ case "queryparametermode":
+ case "queryParameterMode": getOrCreateConfiguration(target).setQueryParameterMode(property(camelContext, java.lang.String.class, value)); return true;
+ case "requestfilter":
+ case "requestFilter": getOrCreateConfiguration(target).setRequestFilter(property(camelContext, java.lang.String.class, value)); return true;
+ case "requesttimeout":
+ case "requestTimeout": getOrCreateConfiguration(target).setRequestTimeout(property(camelContext, long.class, value)); return true;
+ case "restpostmanprocessorstrategy":
+ case "restPostmanProcessorStrategy": target.setRestPostmanProcessorStrategy(property(camelContext, org.apache.camel.component.rest.postman.RestPostmanProcessorStrategy.class, value)); return true;
+ case "runfailfast":
+ case "runFailFast": getOrCreateConfiguration(target).setRunFailFast(property(camelContext, boolean.class, value)); return true;
+ case "sslcontextparameters":
+ case "sslContextParameters": getOrCreateConfiguration(target).setSslContextParameters(property(camelContext, org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": getOrCreateConfiguration(target).setUseGlobalSslContextParameters(property(camelContext, boolean.class, value)); return true;
+ case "variables": getOrCreateConfiguration(target).setVariables(property(camelContext, java.util.Map.class, value)); return true;
+ default: return false;
+ }
+ }
+
+ @Override
+ public Class> getOptionType(String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": return java.lang.String.class;
+ case "autowiredenabled":
+ case "autowiredEnabled": return boolean.class;
+ case "basepath":
+ case "basePath": return java.lang.String.class;
+ case "bridgeerrorhandler":
+ case "bridgeErrorHandler": return boolean.class;
+ case "clientrequestvalidation":
+ case "clientRequestValidation": return boolean.class;
+ case "collectionauth":
+ case "collectionAuth": return java.lang.String.class;
+ case "collectioncachettl":
+ case "collectionCacheTtl": return long.class;
+ case "collectionsource":
+ case "collectionSource": return java.lang.String.class;
+ case "collectionsourcetype":
+ case "collectionSourceType": return java.lang.String.class;
+ case "componentname":
+ case "componentName": return java.lang.String.class;
+ case "configuration": return org.apache.camel.component.rest.postman.RestPostmanConfiguration.class;
+ case "connecttimeout":
+ case "connectTimeout": return long.class;
+ case "consumercomponentname":
+ case "consumerComponentName": return java.lang.String.class;
+ case "consumes": return java.lang.String.class;
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": return boolean.class;
+ case "host": return java.lang.String.class;
+ case "lazystartproducer":
+ case "lazyStartProducer": return boolean.class;
+ case "missingrequest":
+ case "missingRequest": return java.lang.String.class;
+ case "mockincludepattern":
+ case "mockIncludePattern": return java.lang.String.class;
+ case "oauthprofile":
+ case "oauthProfile": return java.lang.String.class;
+ case "postmanapikey":
+ case "postmanApiKey": return java.lang.String.class;
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": return java.lang.String.class;
+ case "postmanapiurl":
+ case "postmanApiUrl": return java.lang.String.class;
+ case "produces": return java.lang.String.class;
+ case "queryparametermode":
+ case "queryParameterMode": return java.lang.String.class;
+ case "requestfilter":
+ case "requestFilter": return java.lang.String.class;
+ case "requesttimeout":
+ case "requestTimeout": return long.class;
+ case "restpostmanprocessorstrategy":
+ case "restPostmanProcessorStrategy": return org.apache.camel.component.rest.postman.RestPostmanProcessorStrategy.class;
+ case "runfailfast":
+ case "runFailFast": return boolean.class;
+ case "sslcontextparameters":
+ case "sslContextParameters": return org.apache.camel.support.jsse.SSLContextParameters.class;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return boolean.class;
+ case "variables": return java.util.Map.class;
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+ RestPostmanComponent target = (RestPostmanComponent) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": return getOrCreateConfiguration(target).getApiContextPath();
+ case "autowiredenabled":
+ case "autowiredEnabled": return target.isAutowiredEnabled();
+ case "basepath":
+ case "basePath": return getOrCreateConfiguration(target).getBasePath();
+ case "bridgeerrorhandler":
+ case "bridgeErrorHandler": return target.isBridgeErrorHandler();
+ case "clientrequestvalidation":
+ case "clientRequestValidation": return getOrCreateConfiguration(target).isClientRequestValidation();
+ case "collectionauth":
+ case "collectionAuth": return getOrCreateConfiguration(target).getCollectionAuth();
+ case "collectioncachettl":
+ case "collectionCacheTtl": return getOrCreateConfiguration(target).getCollectionCacheTtl();
+ case "collectionsource":
+ case "collectionSource": return target.getCollectionSource();
+ case "collectionsourcetype":
+ case "collectionSourceType": return getOrCreateConfiguration(target).getCollectionSourceType();
+ case "componentname":
+ case "componentName": return getOrCreateConfiguration(target).getComponentName();
+ case "configuration": return target.getConfiguration();
+ case "connecttimeout":
+ case "connectTimeout": return getOrCreateConfiguration(target).getConnectTimeout();
+ case "consumercomponentname":
+ case "consumerComponentName": return getOrCreateConfiguration(target).getConsumerComponentName();
+ case "consumes": return getOrCreateConfiguration(target).getConsumes();
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": return getOrCreateConfiguration(target).isFailOnUnresolvedVariable();
+ case "host": return getOrCreateConfiguration(target).getHost();
+ case "lazystartproducer":
+ case "lazyStartProducer": return target.isLazyStartProducer();
+ case "missingrequest":
+ case "missingRequest": return getOrCreateConfiguration(target).getMissingRequest();
+ case "mockincludepattern":
+ case "mockIncludePattern": return getOrCreateConfiguration(target).getMockIncludePattern();
+ case "oauthprofile":
+ case "oauthProfile": return getOrCreateConfiguration(target).getOauthProfile();
+ case "postmanapikey":
+ case "postmanApiKey": return getOrCreateConfiguration(target).getPostmanApiKey();
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": return getOrCreateConfiguration(target).getPostmanApiKeyHeader();
+ case "postmanapiurl":
+ case "postmanApiUrl": return getOrCreateConfiguration(target).getPostmanApiUrl();
+ case "produces": return getOrCreateConfiguration(target).getProduces();
+ case "queryparametermode":
+ case "queryParameterMode": return getOrCreateConfiguration(target).getQueryParameterMode();
+ case "requestfilter":
+ case "requestFilter": return getOrCreateConfiguration(target).getRequestFilter();
+ case "requesttimeout":
+ case "requestTimeout": return getOrCreateConfiguration(target).getRequestTimeout();
+ case "restpostmanprocessorstrategy":
+ case "restPostmanProcessorStrategy": return target.getRestPostmanProcessorStrategy();
+ case "runfailfast":
+ case "runFailFast": return getOrCreateConfiguration(target).isRunFailFast();
+ case "sslcontextparameters":
+ case "sslContextParameters": return getOrCreateConfiguration(target).getSslContextParameters();
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return getOrCreateConfiguration(target).isUseGlobalSslContextParameters();
+ case "variables": return getOrCreateConfiguration(target).getVariables();
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getCollectionValueType(Object target, String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "variables": return java.lang.Object.class;
+ default: return null;
+ }
+ }
+}
+
diff --git a/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanConfigurationConfigurer.java b/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanConfigurationConfigurer.java
new file mode 100644
index 0000000000000..c5536f3151314
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanConfigurationConfigurer.java
@@ -0,0 +1,234 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.rest.postman;
+
+import javax.annotation.processing.Generated;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.component.rest.postman.RestPostmanConfiguration;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class RestPostmanConfigurationConfigurer extends org.apache.camel.support.component.PropertyConfigurerSupport implements GeneratedPropertyConfigurer, ExtendedPropertyConfigurerGetter {
+
+ private static final Map ALL_OPTIONS;
+ static {
+ Map map = new CaseInsensitiveMap();
+ map.put("ApiContextPath", java.lang.String.class);
+ map.put("BasePath", java.lang.String.class);
+ map.put("ClientRequestValidation", boolean.class);
+ map.put("CollectionAuth", java.lang.String.class);
+ map.put("CollectionCacheTtl", long.class);
+ map.put("CollectionSourceType", java.lang.String.class);
+ map.put("ComponentName", java.lang.String.class);
+ map.put("ConnectTimeout", long.class);
+ map.put("ConsumerComponentName", java.lang.String.class);
+ map.put("Consumes", java.lang.String.class);
+ map.put("FailOnUnresolvedVariable", boolean.class);
+ map.put("Host", java.lang.String.class);
+ map.put("MissingRequest", java.lang.String.class);
+ map.put("MockIncludePattern", java.lang.String.class);
+ map.put("OauthProfile", java.lang.String.class);
+ map.put("PostmanApiKey", java.lang.String.class);
+ map.put("PostmanApiKeyHeader", java.lang.String.class);
+ map.put("PostmanApiUrl", java.lang.String.class);
+ map.put("Produces", java.lang.String.class);
+ map.put("QueryParameterMode", java.lang.String.class);
+ map.put("RequestFilter", java.lang.String.class);
+ map.put("RequestTimeout", long.class);
+ map.put("RunFailFast", boolean.class);
+ map.put("SslContextParameters", org.apache.camel.support.jsse.SSLContextParameters.class);
+ map.put("UseGlobalSslContextParameters", boolean.class);
+ map.put("Variables", java.util.Map.class);
+ ALL_OPTIONS = map;
+ }
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) {
+ org.apache.camel.component.rest.postman.RestPostmanConfiguration target = (org.apache.camel.component.rest.postman.RestPostmanConfiguration) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": target.setApiContextPath(property(camelContext, java.lang.String.class, value)); return true;
+ case "basepath":
+ case "basePath": target.setBasePath(property(camelContext, java.lang.String.class, value)); return true;
+ case "clientrequestvalidation":
+ case "clientRequestValidation": target.setClientRequestValidation(property(camelContext, boolean.class, value)); return true;
+ case "collectionauth":
+ case "collectionAuth": target.setCollectionAuth(property(camelContext, java.lang.String.class, value)); return true;
+ case "collectioncachettl":
+ case "collectionCacheTtl": target.setCollectionCacheTtl(property(camelContext, long.class, value)); return true;
+ case "collectionsourcetype":
+ case "collectionSourceType": target.setCollectionSourceType(property(camelContext, java.lang.String.class, value)); return true;
+ case "componentname":
+ case "componentName": target.setComponentName(property(camelContext, java.lang.String.class, value)); return true;
+ case "connecttimeout":
+ case "connectTimeout": target.setConnectTimeout(property(camelContext, long.class, value)); return true;
+ case "consumercomponentname":
+ case "consumerComponentName": target.setConsumerComponentName(property(camelContext, java.lang.String.class, value)); return true;
+ case "consumes": target.setConsumes(property(camelContext, java.lang.String.class, value)); return true;
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": target.setFailOnUnresolvedVariable(property(camelContext, boolean.class, value)); return true;
+ case "host": target.setHost(property(camelContext, java.lang.String.class, value)); return true;
+ case "missingrequest":
+ case "missingRequest": target.setMissingRequest(property(camelContext, java.lang.String.class, value)); return true;
+ case "mockincludepattern":
+ case "mockIncludePattern": target.setMockIncludePattern(property(camelContext, java.lang.String.class, value)); return true;
+ case "oauthprofile":
+ case "oauthProfile": target.setOauthProfile(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapikey":
+ case "postmanApiKey": target.setPostmanApiKey(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": target.setPostmanApiKeyHeader(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapiurl":
+ case "postmanApiUrl": target.setPostmanApiUrl(property(camelContext, java.lang.String.class, value)); return true;
+ case "produces": target.setProduces(property(camelContext, java.lang.String.class, value)); return true;
+ case "queryparametermode":
+ case "queryParameterMode": target.setQueryParameterMode(property(camelContext, java.lang.String.class, value)); return true;
+ case "requestfilter":
+ case "requestFilter": target.setRequestFilter(property(camelContext, java.lang.String.class, value)); return true;
+ case "requesttimeout":
+ case "requestTimeout": target.setRequestTimeout(property(camelContext, long.class, value)); return true;
+ case "runfailfast":
+ case "runFailFast": target.setRunFailFast(property(camelContext, boolean.class, value)); return true;
+ case "sslcontextparameters":
+ case "sslContextParameters": target.setSslContextParameters(property(camelContext, org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": target.setUseGlobalSslContextParameters(property(camelContext, boolean.class, value)); return true;
+ case "variables": target.setVariables(property(camelContext, java.util.Map.class, value)); return true;
+ default: return false;
+ }
+ }
+
+ @Override
+ public Map getAllOptions(Object target) {
+ return ALL_OPTIONS;
+ }
+
+ @Override
+ public Class> getOptionType(String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": return java.lang.String.class;
+ case "basepath":
+ case "basePath": return java.lang.String.class;
+ case "clientrequestvalidation":
+ case "clientRequestValidation": return boolean.class;
+ case "collectionauth":
+ case "collectionAuth": return java.lang.String.class;
+ case "collectioncachettl":
+ case "collectionCacheTtl": return long.class;
+ case "collectionsourcetype":
+ case "collectionSourceType": return java.lang.String.class;
+ case "componentname":
+ case "componentName": return java.lang.String.class;
+ case "connecttimeout":
+ case "connectTimeout": return long.class;
+ case "consumercomponentname":
+ case "consumerComponentName": return java.lang.String.class;
+ case "consumes": return java.lang.String.class;
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": return boolean.class;
+ case "host": return java.lang.String.class;
+ case "missingrequest":
+ case "missingRequest": return java.lang.String.class;
+ case "mockincludepattern":
+ case "mockIncludePattern": return java.lang.String.class;
+ case "oauthprofile":
+ case "oauthProfile": return java.lang.String.class;
+ case "postmanapikey":
+ case "postmanApiKey": return java.lang.String.class;
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": return java.lang.String.class;
+ case "postmanapiurl":
+ case "postmanApiUrl": return java.lang.String.class;
+ case "produces": return java.lang.String.class;
+ case "queryparametermode":
+ case "queryParameterMode": return java.lang.String.class;
+ case "requestfilter":
+ case "requestFilter": return java.lang.String.class;
+ case "requesttimeout":
+ case "requestTimeout": return long.class;
+ case "runfailfast":
+ case "runFailFast": return boolean.class;
+ case "sslcontextparameters":
+ case "sslContextParameters": return org.apache.camel.support.jsse.SSLContextParameters.class;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return boolean.class;
+ case "variables": return java.util.Map.class;
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+ org.apache.camel.component.rest.postman.RestPostmanConfiguration target = (org.apache.camel.component.rest.postman.RestPostmanConfiguration) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": return target.getApiContextPath();
+ case "basepath":
+ case "basePath": return target.getBasePath();
+ case "clientrequestvalidation":
+ case "clientRequestValidation": return target.isClientRequestValidation();
+ case "collectionauth":
+ case "collectionAuth": return target.getCollectionAuth();
+ case "collectioncachettl":
+ case "collectionCacheTtl": return target.getCollectionCacheTtl();
+ case "collectionsourcetype":
+ case "collectionSourceType": return target.getCollectionSourceType();
+ case "componentname":
+ case "componentName": return target.getComponentName();
+ case "connecttimeout":
+ case "connectTimeout": return target.getConnectTimeout();
+ case "consumercomponentname":
+ case "consumerComponentName": return target.getConsumerComponentName();
+ case "consumes": return target.getConsumes();
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": return target.isFailOnUnresolvedVariable();
+ case "host": return target.getHost();
+ case "missingrequest":
+ case "missingRequest": return target.getMissingRequest();
+ case "mockincludepattern":
+ case "mockIncludePattern": return target.getMockIncludePattern();
+ case "oauthprofile":
+ case "oauthProfile": return target.getOauthProfile();
+ case "postmanapikey":
+ case "postmanApiKey": return target.getPostmanApiKey();
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": return target.getPostmanApiKeyHeader();
+ case "postmanapiurl":
+ case "postmanApiUrl": return target.getPostmanApiUrl();
+ case "produces": return target.getProduces();
+ case "queryparametermode":
+ case "queryParameterMode": return target.getQueryParameterMode();
+ case "requestfilter":
+ case "requestFilter": return target.getRequestFilter();
+ case "requesttimeout":
+ case "requestTimeout": return target.getRequestTimeout();
+ case "runfailfast":
+ case "runFailFast": return target.isRunFailFast();
+ case "sslcontextparameters":
+ case "sslContextParameters": return target.getSslContextParameters();
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return target.isUseGlobalSslContextParameters();
+ case "variables": return target.getVariables();
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getCollectionValueType(Object target, String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "variables": return java.lang.Object.class;
+ default: return null;
+ }
+ }
+}
+
diff --git a/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanEndpointConfigurer.java b/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanEndpointConfigurer.java
new file mode 100644
index 0000000000000..1083cdca9b81a
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanEndpointConfigurer.java
@@ -0,0 +1,221 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.rest.postman;
+
+import javax.annotation.processing.Generated;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.support.component.PropertyConfigurerSupport;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.EndpointSchemaGeneratorMojo")
+@SuppressWarnings("unchecked")
+public class RestPostmanEndpointConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+ @Override
+ public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) {
+ RestPostmanEndpoint target = (RestPostmanEndpoint) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": target.getConfiguration().setApiContextPath(property(camelContext, java.lang.String.class, value)); return true;
+ case "basepath":
+ case "basePath": target.getConfiguration().setBasePath(property(camelContext, java.lang.String.class, value)); return true;
+ case "bridgeerrorhandler":
+ case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true;
+ case "clientrequestvalidation":
+ case "clientRequestValidation": target.getConfiguration().setClientRequestValidation(property(camelContext, boolean.class, value)); return true;
+ case "collectionauth":
+ case "collectionAuth": target.getConfiguration().setCollectionAuth(property(camelContext, java.lang.String.class, value)); return true;
+ case "collectioncachettl":
+ case "collectionCacheTtl": target.getConfiguration().setCollectionCacheTtl(property(camelContext, long.class, value)); return true;
+ case "collectionsourcetype":
+ case "collectionSourceType": target.getConfiguration().setCollectionSourceType(property(camelContext, java.lang.String.class, value)); return true;
+ case "componentname":
+ case "componentName": target.getConfiguration().setComponentName(property(camelContext, java.lang.String.class, value)); return true;
+ case "connecttimeout":
+ case "connectTimeout": target.getConfiguration().setConnectTimeout(property(camelContext, long.class, value)); return true;
+ case "consumercomponentname":
+ case "consumerComponentName": target.getConfiguration().setConsumerComponentName(property(camelContext, java.lang.String.class, value)); return true;
+ case "consumes": target.getConfiguration().setConsumes(property(camelContext, java.lang.String.class, value)); return true;
+ case "exceptionhandler":
+ case "exceptionHandler": target.setExceptionHandler(property(camelContext, org.apache.camel.spi.ExceptionHandler.class, value)); return true;
+ case "exchangepattern":
+ case "exchangePattern": target.setExchangePattern(property(camelContext, org.apache.camel.ExchangePattern.class, value)); return true;
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": target.getConfiguration().setFailOnUnresolvedVariable(property(camelContext, boolean.class, value)); return true;
+ case "host": target.getConfiguration().setHost(property(camelContext, java.lang.String.class, value)); return true;
+ case "lazystartproducer":
+ case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true;
+ case "missingrequest":
+ case "missingRequest": target.getConfiguration().setMissingRequest(property(camelContext, java.lang.String.class, value)); return true;
+ case "mockincludepattern":
+ case "mockIncludePattern": target.getConfiguration().setMockIncludePattern(property(camelContext, java.lang.String.class, value)); return true;
+ case "oauthprofile":
+ case "oauthProfile": target.getConfiguration().setOauthProfile(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapikey":
+ case "postmanApiKey": target.getConfiguration().setPostmanApiKey(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": target.getConfiguration().setPostmanApiKeyHeader(property(camelContext, java.lang.String.class, value)); return true;
+ case "postmanapiurl":
+ case "postmanApiUrl": target.getConfiguration().setPostmanApiUrl(property(camelContext, java.lang.String.class, value)); return true;
+ case "produces": target.getConfiguration().setProduces(property(camelContext, java.lang.String.class, value)); return true;
+ case "queryparametermode":
+ case "queryParameterMode": target.getConfiguration().setQueryParameterMode(property(camelContext, java.lang.String.class, value)); return true;
+ case "requestfilter":
+ case "requestFilter": target.getConfiguration().setRequestFilter(property(camelContext, java.lang.String.class, value)); return true;
+ case "requesttimeout":
+ case "requestTimeout": target.getConfiguration().setRequestTimeout(property(camelContext, long.class, value)); return true;
+ case "runfailfast":
+ case "runFailFast": target.getConfiguration().setRunFailFast(property(camelContext, boolean.class, value)); return true;
+ case "sslcontextparameters":
+ case "sslContextParameters": target.getConfiguration().setSslContextParameters(property(camelContext, org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": target.getConfiguration().setUseGlobalSslContextParameters(property(camelContext, boolean.class, value)); return true;
+ case "variables": target.getConfiguration().setVariables(property(camelContext, java.util.Map.class, value)); return true;
+ default: return false;
+ }
+ }
+
+ @Override
+ public Class> getOptionType(String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": return java.lang.String.class;
+ case "basepath":
+ case "basePath": return java.lang.String.class;
+ case "bridgeerrorhandler":
+ case "bridgeErrorHandler": return boolean.class;
+ case "clientrequestvalidation":
+ case "clientRequestValidation": return boolean.class;
+ case "collectionauth":
+ case "collectionAuth": return java.lang.String.class;
+ case "collectioncachettl":
+ case "collectionCacheTtl": return long.class;
+ case "collectionsourcetype":
+ case "collectionSourceType": return java.lang.String.class;
+ case "componentname":
+ case "componentName": return java.lang.String.class;
+ case "connecttimeout":
+ case "connectTimeout": return long.class;
+ case "consumercomponentname":
+ case "consumerComponentName": return java.lang.String.class;
+ case "consumes": return java.lang.String.class;
+ case "exceptionhandler":
+ case "exceptionHandler": return org.apache.camel.spi.ExceptionHandler.class;
+ case "exchangepattern":
+ case "exchangePattern": return org.apache.camel.ExchangePattern.class;
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": return boolean.class;
+ case "host": return java.lang.String.class;
+ case "lazystartproducer":
+ case "lazyStartProducer": return boolean.class;
+ case "missingrequest":
+ case "missingRequest": return java.lang.String.class;
+ case "mockincludepattern":
+ case "mockIncludePattern": return java.lang.String.class;
+ case "oauthprofile":
+ case "oauthProfile": return java.lang.String.class;
+ case "postmanapikey":
+ case "postmanApiKey": return java.lang.String.class;
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": return java.lang.String.class;
+ case "postmanapiurl":
+ case "postmanApiUrl": return java.lang.String.class;
+ case "produces": return java.lang.String.class;
+ case "queryparametermode":
+ case "queryParameterMode": return java.lang.String.class;
+ case "requestfilter":
+ case "requestFilter": return java.lang.String.class;
+ case "requesttimeout":
+ case "requestTimeout": return long.class;
+ case "runfailfast":
+ case "runFailFast": return boolean.class;
+ case "sslcontextparameters":
+ case "sslContextParameters": return org.apache.camel.support.jsse.SSLContextParameters.class;
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return boolean.class;
+ case "variables": return java.util.Map.class;
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+ RestPostmanEndpoint target = (RestPostmanEndpoint) obj;
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "apicontextpath":
+ case "apiContextPath": return target.getConfiguration().getApiContextPath();
+ case "basepath":
+ case "basePath": return target.getConfiguration().getBasePath();
+ case "bridgeerrorhandler":
+ case "bridgeErrorHandler": return target.isBridgeErrorHandler();
+ case "clientrequestvalidation":
+ case "clientRequestValidation": return target.getConfiguration().isClientRequestValidation();
+ case "collectionauth":
+ case "collectionAuth": return target.getConfiguration().getCollectionAuth();
+ case "collectioncachettl":
+ case "collectionCacheTtl": return target.getConfiguration().getCollectionCacheTtl();
+ case "collectionsourcetype":
+ case "collectionSourceType": return target.getConfiguration().getCollectionSourceType();
+ case "componentname":
+ case "componentName": return target.getConfiguration().getComponentName();
+ case "connecttimeout":
+ case "connectTimeout": return target.getConfiguration().getConnectTimeout();
+ case "consumercomponentname":
+ case "consumerComponentName": return target.getConfiguration().getConsumerComponentName();
+ case "consumes": return target.getConfiguration().getConsumes();
+ case "exceptionhandler":
+ case "exceptionHandler": return target.getExceptionHandler();
+ case "exchangepattern":
+ case "exchangePattern": return target.getExchangePattern();
+ case "failonunresolvedvariable":
+ case "failOnUnresolvedVariable": return target.getConfiguration().isFailOnUnresolvedVariable();
+ case "host": return target.getConfiguration().getHost();
+ case "lazystartproducer":
+ case "lazyStartProducer": return target.isLazyStartProducer();
+ case "missingrequest":
+ case "missingRequest": return target.getConfiguration().getMissingRequest();
+ case "mockincludepattern":
+ case "mockIncludePattern": return target.getConfiguration().getMockIncludePattern();
+ case "oauthprofile":
+ case "oauthProfile": return target.getConfiguration().getOauthProfile();
+ case "postmanapikey":
+ case "postmanApiKey": return target.getConfiguration().getPostmanApiKey();
+ case "postmanapikeyheader":
+ case "postmanApiKeyHeader": return target.getConfiguration().getPostmanApiKeyHeader();
+ case "postmanapiurl":
+ case "postmanApiUrl": return target.getConfiguration().getPostmanApiUrl();
+ case "produces": return target.getConfiguration().getProduces();
+ case "queryparametermode":
+ case "queryParameterMode": return target.getConfiguration().getQueryParameterMode();
+ case "requestfilter":
+ case "requestFilter": return target.getConfiguration().getRequestFilter();
+ case "requesttimeout":
+ case "requestTimeout": return target.getConfiguration().getRequestTimeout();
+ case "runfailfast":
+ case "runFailFast": return target.getConfiguration().isRunFailFast();
+ case "sslcontextparameters":
+ case "sslContextParameters": return target.getConfiguration().getSslContextParameters();
+ case "useglobalsslcontextparameters":
+ case "useGlobalSslContextParameters": return target.getConfiguration().isUseGlobalSslContextParameters();
+ case "variables": return target.getConfiguration().getVariables();
+ default: return null;
+ }
+ }
+
+ @Override
+ public Object getCollectionValueType(Object target, String name, boolean ignoreCase) {
+ switch (ignoreCase ? name.toLowerCase() : name) {
+ case "variables": return java.lang.Object.class;
+ default: return null;
+ }
+ }
+}
+
diff --git a/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanEndpointUriFactory.java b/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanEndpointUriFactory.java
new file mode 100644
index 0000000000000..d2bee70eec09d
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/java/org/apache/camel/component/rest/postman/RestPostmanEndpointUriFactory.java
@@ -0,0 +1,113 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.component.rest.postman;
+
+import javax.annotation.processing.Generated;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.spi.EndpointUriFactory;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateEndpointUriFactoryMojo")
+public class RestPostmanEndpointUriFactory extends org.apache.camel.support.component.EndpointUriFactorySupport implements EndpointUriFactory {
+
+ private static final String BASE = ":collectionSource#requestId";
+
+ private static final Set PROPERTY_NAMES;
+ private static final Set SECRET_PROPERTY_NAMES;
+ private static final Set ENDPOINT_IDENTITY_PROPERTY_NAMES;
+ private static final Map MULTI_VALUE_PREFIXES;
+ static {
+ Set props = new HashSet<>(32);
+ props.add("apiContextPath");
+ props.add("basePath");
+ props.add("bridgeErrorHandler");
+ props.add("clientRequestValidation");
+ props.add("collectionAuth");
+ props.add("collectionCacheTtl");
+ props.add("collectionSource");
+ props.add("collectionSourceType");
+ props.add("componentName");
+ props.add("connectTimeout");
+ props.add("consumerComponentName");
+ props.add("consumes");
+ props.add("exceptionHandler");
+ props.add("exchangePattern");
+ props.add("failOnUnresolvedVariable");
+ props.add("host");
+ props.add("lazyStartProducer");
+ props.add("missingRequest");
+ props.add("mockIncludePattern");
+ props.add("oauthProfile");
+ props.add("postmanApiKey");
+ props.add("postmanApiKeyHeader");
+ props.add("postmanApiUrl");
+ props.add("produces");
+ props.add("queryParameterMode");
+ props.add("requestFilter");
+ props.add("requestId");
+ props.add("requestTimeout");
+ props.add("runFailFast");
+ props.add("sslContextParameters");
+ props.add("useGlobalSslContextParameters");
+ props.add("variables");
+ PROPERTY_NAMES = Collections.unmodifiableSet(props);
+ Set secretProps = new HashSet<>(1);
+ secretProps.add("postmanApiKey");
+ SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps);
+ ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet();
+ Map prefixes = new HashMap<>(1);
+ prefixes.put("variables", "variable.");
+ MULTI_VALUE_PREFIXES = Collections.unmodifiableMap(prefixes);
+ }
+
+ @Override
+ public boolean isEnabled(String scheme) {
+ return "rest-postman".equals(scheme);
+ }
+
+ @Override
+ public String buildUri(String scheme, Map properties, boolean encode) throws URISyntaxException {
+ String syntax = scheme + BASE;
+ String uri = syntax;
+
+ Map copy = new HashMap<>(properties);
+
+ uri = buildPathParameter(syntax, uri, "collectionSource", "postman-collection.json", false, copy);
+ uri = buildPathParameter(syntax, uri, "requestId", null, false, copy);
+ uri = buildQueryParameters(uri, copy, encode);
+ return uri;
+ }
+
+ @Override
+ public Set propertyNames() {
+ return PROPERTY_NAMES;
+ }
+
+ @Override
+ public Set secretPropertyNames() {
+ return SECRET_PROPERTY_NAMES;
+ }
+
+ @Override
+ public Set endpointIdentityPropertyNames() {
+ return ENDPOINT_IDENTITY_PROPERTY_NAMES;
+ }
+
+ @Override
+ public Map multiValuePrefixes() {
+ return MULTI_VALUE_PREFIXES;
+ }
+
+ @Override
+ public boolean isLenientProperties() {
+ return false;
+ }
+}
+
diff --git a/components/camel-rest-postman/src/generated/resources/META-INF/org/apache/camel/component/rest/postman/rest-postman.json b/components/camel-rest-postman/src/generated/resources/META-INF/org/apache/camel/component/rest/postman/rest-postman.json
new file mode 100644
index 0000000000000..8b2c3ae3d0686
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/resources/META-INF/org/apache/camel/component/rest/postman/rest-postman.json
@@ -0,0 +1,101 @@
+{
+ "component": {
+ "kind": "component",
+ "name": "rest-postman",
+ "title": "REST Postman",
+ "description": "To call and expose REST services using a Postman Collection as contract.",
+ "deprecated": false,
+ "firstVersion": "4.22.0",
+ "label": "rest,api",
+ "javaType": "org.apache.camel.component.rest.postman.RestPostmanComponent",
+ "supportLevel": "Preview",
+ "groupId": "org.apache.camel",
+ "artifactId": "camel-rest-postman",
+ "version": "4.22.0-SNAPSHOT",
+ "scheme": "rest-postman",
+ "extendsScheme": "",
+ "syntax": "rest-postman:collectionSource#requestId",
+ "async": false,
+ "api": false,
+ "consumerOnly": false,
+ "producerOnly": false,
+ "lenientProperties": false,
+ "browsable": false,
+ "remote": true
+ },
+ "componentProperties": {
+ "basePath": { "index": 0, "kind": "property", "displayName": "Base Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "API basePath, for example \/v2. Default is unset, if set overrides the value derived from the request URL in the collection." },
+ "collectionSource": { "index": 1, "kind": "property", "displayName": "Collection Source", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The Postman Collection to use, when it is not given on the endpoint. Either a resource URI of a Collection v2.1 JSON document (classpath:, file: or http:), or the uid of a collection to fetch from the Postman cloud." },
+ "collectionSourceType": { "index": 2, "kind": "property", "displayName": "Collection Source Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "auto", "resource", "cloud" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "auto", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How to interpret collectionSource. With auto, a bare collection UUID or {ownerId}-{uuid} is fetched from the Postman cloud and anything else is resolved as a resource (classpath:, file:, http:). Use resource or cloud to decide explicitly." },
+ "variables": { "index": 3, "kind": "property", "displayName": "Variables", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "variable.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Values for the {{variable}} placeholders used in the collection. These override the variables declared by the collection and its folders. This is a multi-value option with prefix: variable." },
+ "failOnUnresolvedVariable": { "index": 4, "kind": "property", "displayName": "Fail On Unresolved Variable", "group": "common (advanced)", "label": "common,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether to fail if a {{variable}} placeholder used by the selected request cannot be resolved. When false the placeholder is left as-is." },
+ "apiContextPath": { "index": 5, "kind": "property", "displayName": "Api Context Path", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Sets the context-path to use for servicing the Postman collection document. The document is served with all auth blocks and all secret variables removed." },
+ "bridgeErrorHandler": { "index": 6, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "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." },
+ "clientRequestValidation": { "index": 7, "kind": "property", "displayName": "Client Request Validation", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether to enable validation of the client request. A Postman collection has no schemas, so this is a best-effort check of required headers, query parameters and body presence only." },
+ "missingRequest": { "index": 8, "kind": "property", "displayName": "Missing Request", "group": "consumer", "label": "consumer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "fail", "ignore", "mock" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "fail", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether the consumer should fail, ignore or return a mock response for requests in the collection that are not mapped to a corresponding route." },
+ "consumerComponentName": { "index": 9, "kind": "property", "displayName": "Consumer Component Name", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Name of the Camel component that will service the requests. The component must be present in Camel registry and it must be able to service contract-first REST consumers, as platform-http does. If not set CLASSPATH is searched for a single component with that capability." },
+ "mockIncludePattern": { "index": 10, "kind": "property", "displayName": "Mock Include Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "classpath:camel-mock\/**", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Used for inclusive filtering of mock data from directories. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. Saved example responses in the collection are preferred over these files." },
+ "requestFilter": { "index": 11, "kind": "property", "displayName": "Request Filter", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Filters which requests of the collection are used, as comma separated Ant-style patterns matched against the folder qualified request id. Prefix a pattern with ! to exclude." },
+ "restPostmanProcessorStrategy": { "index": 12, "kind": "property", "displayName": "Rest Postman Processor Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.rest.postman.RestPostmanProcessorStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom strategy for how to service the requests of the collection." },
+ "host": { "index": 13, "kind": "property", "displayName": "Host", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Scheme hostname and port to direct the HTTP requests to in the form of https:\/\/hostname:port. If set overrides any value derived from the collection." },
+ "lazyStartProducer": { "index": 14, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." },
+ "runFailFast": { "index": 15, "kind": "property", "displayName": "Run Fail Fast", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "When the endpoint runs more than one request, that is when it selects a folder or the whole collection, whether to stop and fail on the first request that fails. When false every request is attempted and the failure is recorded in its result." },
+ "componentName": { "index": 16, "kind": "property", "displayName": "Component Name", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Name of the Camel component that will perform the requests. The component must be present in Camel registry and it must implement RestProducerFactory service provider interface. If not set CLASSPATH is searched for single component that implements RestProducerFactory SPI." },
+ "consumes": { "index": 17, "kind": "property", "displayName": "Consumes", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What payload type this component is capable of consuming. This equates to the value of the Accept HTTP header. A Postman collection does not describe responses, so unlike an OpenAPI specification there is nothing to infer this from and it is unset by default." },
+ "produces": { "index": 18, "kind": "property", "displayName": "Produces", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What payload type this component is producing. This equates to the value of the Content-Type HTTP header. If not set it is inferred from the body mode of the request in the collection." },
+ "queryParameterMode": { "index": 19, "kind": "property", "displayName": "Query Parameter Mode", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "placeholder", "literal" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "placeholder", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How to treat the query parameters declared in the collection. With placeholder the parameter names are bound to message headers and the values in the collection are ignored as sample data. With literal the values in the collection are sent as-is." },
+ "autowiredEnabled": { "index": 20, "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." },
+ "collectionCacheTtl": { "index": 21, "kind": "property", "displayName": "Collection Cache Ttl", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": -1, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How long a loaded collection is cached, in milliseconds. Use -1 to cache for the lifetime of the component." },
+ "configuration": { "index": 22, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The shared configuration used as the template for every endpoint created by this component." },
+ "connectTimeout": { "index": 23, "kind": "property", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 15000, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Connection timeout in milliseconds when fetching a collection from the Postman cloud." },
+ "requestTimeout": { "index": 24, "kind": "property", "displayName": "Request Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds when fetching a collection from the Postman cloud." },
+ "collectionAuth": { "index": 25, "kind": "property", "displayName": "Collection Auth", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ignore", "header", "fail" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "ignore", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What to do with the auth block the collection declares for the target API. With ignore the block is not applied, and a warning names the type that was found. With header the basic, bearer and apikey types are applied as a static header or query parameter, and any other type fails at startup rather than silently sending no credential. With fail any auth block other than noauth is rejected." },
+ "oauthProfile": { "index": 26, "kind": "property", "displayName": "OAuth Profile", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The OAuth profile to use for authenticating the incoming requests. The profile is enforced by the consumer component servicing the requests." },
+ "postmanApiKey": { "index": 27, "kind": "property", "displayName": "Postman Api Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The Postman API key used to fetch the collection from the Postman cloud. This credential authenticates against Postman itself and is never sent to the API the collection describes." },
+ "postmanApiKeyHeader": { "index": 28, "kind": "property", "displayName": "Postman Api Key Header", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "X-Api-Key", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The HTTP header used to send the Postman API key when fetching a collection." },
+ "sslContextParameters": { "index": 29, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Customize TLS parameters used by the component. If not set defaults to the TLS parameters set in the Camel context. These parameters are used both when fetching a collection from the Postman cloud and by the delegate producer." },
+ "useGlobalSslContextParameters": { "index": 30, "kind": "property", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Enable usage of global SSL context parameters." },
+ "postmanApiUrl": { "index": 31, "kind": "property", "displayName": "Postman Api Url", "group": "security (advanced)", "label": "security,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "https:\/\/api.getpostman.com", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The base URL of the Postman API used to fetch collections. Must use https, except for localhost, because plain http would send the Postman API key in clear text." }
+ },
+ "headers": {
+ "CamelRestPostmanRequestId": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The id of the Postman request being invoked or serviced.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#REQUEST_ID" },
+ "CamelRestPostmanRequestName": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The name of the Postman request, as written in the collection.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#REQUEST_NAME" },
+ "CamelRestPostmanFolderPath": { "index": 2, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The folder path of the Postman request, with folders separated by a slash.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#FOLDER_PATH" },
+ "CamelRestPostmanRequestCount": { "index": 3, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of requests executed when running a folder or a whole collection.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#REQUEST_COUNT" },
+ "CamelRestPostmanFailedCount": { "index": 4, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of requests that failed when running a folder or a whole collection with runFailFast disabled.", "constantName": "org.apache.camel.component.rest.postman.RestPostmanConstants#FAILED_COUNT" }
+ },
+ "properties": {
+ "collectionSource": { "index": 0, "kind": "path", "displayName": "Collection Source", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "postman-collection.json", "description": "The Postman Collection to use. Either a resource URI of a Collection v2.1 JSON document (classpath:, file: or http:), or the uid of a collection to fetch from the Postman cloud, which requires postmanApiKey. Default value notice: By default loads the postman-collection.json file" },
+ "requestId": { "index": 1, "kind": "path", "displayName": "Request Id", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The request to invoke, identified by its id in the collection or by its slugified name, for example getUserById. Use a folder id to run every request in that folder, and leave it out to run the whole collection. Append a slash to force a folder match when a request and a folder share a name." },
+ "basePath": { "index": 2, "kind": "parameter", "displayName": "Base Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "API basePath, for example \/v2. Default is unset, if set overrides the value derived from the request URL in the collection." },
+ "collectionSourceType": { "index": 3, "kind": "parameter", "displayName": "Collection Source Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "auto", "resource", "cloud" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "auto", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How to interpret collectionSource. With auto, a bare collection UUID or {ownerId}-{uuid} is fetched from the Postman cloud and anything else is resolved as a resource (classpath:, file:, http:). Use resource or cloud to decide explicitly." },
+ "variables": { "index": 4, "kind": "parameter", "displayName": "Variables", "group": "common", "label": "common", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "variable.", "multiValue": true, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Values for the {{variable}} placeholders used in the collection. These override the variables declared by the collection and its folders. This is a multi-value option with prefix: variable." },
+ "failOnUnresolvedVariable": { "index": 5, "kind": "parameter", "displayName": "Fail On Unresolved Variable", "group": "common (advanced)", "label": "common,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether to fail if a {{variable}} placeholder used by the selected request cannot be resolved. When false the placeholder is left as-is." },
+ "apiContextPath": { "index": 6, "kind": "parameter", "displayName": "Api Context Path", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Sets the context-path to use for servicing the Postman collection document. The document is served with all auth blocks and all secret variables removed." },
+ "clientRequestValidation": { "index": 7, "kind": "parameter", "displayName": "Client Request Validation", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether to enable validation of the client request. A Postman collection has no schemas, so this is a best-effort check of required headers, query parameters and body presence only." },
+ "missingRequest": { "index": 8, "kind": "parameter", "displayName": "Missing Request", "group": "consumer", "label": "consumer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "fail", "ignore", "mock" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "fail", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Whether the consumer should fail, ignore or return a mock response for requests in the collection that are not mapped to a corresponding route." },
+ "bridgeErrorHandler": { "index": 9, "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." },
+ "consumerComponentName": { "index": 10, "kind": "parameter", "displayName": "Consumer Component Name", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Name of the Camel component that will service the requests. The component must be present in Camel registry and it must be able to service contract-first REST consumers, as platform-http does. If not set CLASSPATH is searched for a single component with that capability." },
+ "exceptionHandler": { "index": 11, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored." },
+ "exchangePattern": { "index": 12, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." },
+ "mockIncludePattern": { "index": 13, "kind": "parameter", "displayName": "Mock Include Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "classpath:camel-mock\/**", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Used for inclusive filtering of mock data from directories. The pattern is using Ant-path style pattern. Multiple patterns can be specified separated by comma. Saved example responses in the collection are preferred over these files." },
+ "requestFilter": { "index": 14, "kind": "parameter", "displayName": "Request Filter", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Filters which requests of the collection are used, as comma separated Ant-style patterns matched against the folder qualified request id. Prefix a pattern with ! to exclude." },
+ "host": { "index": 15, "kind": "parameter", "displayName": "Host", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Scheme hostname and port to direct the HTTP requests to in the form of https:\/\/hostname:port. If set overrides any value derived from the collection." },
+ "runFailFast": { "index": 16, "kind": "parameter", "displayName": "Run Fail Fast", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "When the endpoint runs more than one request, that is when it selects a folder or the whole collection, whether to stop and fail on the first request that fails. When false every request is attempted and the failure is recorded in its result." },
+ "componentName": { "index": 17, "kind": "parameter", "displayName": "Component Name", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Name of the Camel component that will perform the requests. The component must be present in Camel registry and it must implement RestProducerFactory service provider interface. If not set CLASSPATH is searched for single component that implements RestProducerFactory SPI." },
+ "consumes": { "index": 18, "kind": "parameter", "displayName": "Consumes", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What payload type this component is capable of consuming. This equates to the value of the Accept HTTP header. A Postman collection does not describe responses, so unlike an OpenAPI specification there is nothing to infer this from and it is unset by default." },
+ "lazyStartProducer": { "index": 19, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." },
+ "produces": { "index": 20, "kind": "parameter", "displayName": "Produces", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What payload type this component is producing. This equates to the value of the Content-Type HTTP header. If not set it is inferred from the body mode of the request in the collection." },
+ "queryParameterMode": { "index": 21, "kind": "parameter", "displayName": "Query Parameter Mode", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "placeholder", "literal" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "placeholder", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How to treat the query parameters declared in the collection. With placeholder the parameter names are bound to message headers and the values in the collection are ignored as sample data. With literal the values in the collection are sent as-is." },
+ "collectionCacheTtl": { "index": 22, "kind": "parameter", "displayName": "Collection Cache Ttl", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": -1, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "How long a loaded collection is cached, in milliseconds. Use -1 to cache for the lifetime of the component." },
+ "connectTimeout": { "index": 23, "kind": "parameter", "displayName": "Connect Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 15000, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Connection timeout in milliseconds when fetching a collection from the Postman cloud." },
+ "requestTimeout": { "index": 24, "kind": "parameter", "displayName": "Request Timeout", "group": "advanced", "label": "advanced", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": 30000, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Request timeout in milliseconds when fetching a collection from the Postman cloud." },
+ "collectionAuth": { "index": 25, "kind": "parameter", "displayName": "Collection Auth", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "ignore", "header", "fail" ], "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "ignore", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "What to do with the auth block the collection declares for the target API. With ignore the block is not applied, and a warning names the type that was found. With header the basic, bearer and apikey types are applied as a static header or query parameter, and any other type fails at startup rather than silently sending no credential. With fail any auth block other than noauth is rejected." },
+ "oauthProfile": { "index": 26, "kind": "parameter", "displayName": "OAuth Profile", "group": "security", "label": "consumer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The OAuth profile to use for authenticating the incoming requests. The profile is enforced by the consumer component servicing the requests." },
+ "postmanApiKey": { "index": 27, "kind": "parameter", "displayName": "Postman Api Key", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The Postman API key used to fetch the collection from the Postman cloud. This credential authenticates against Postman itself and is never sent to the API the collection describes." },
+ "postmanApiKeyHeader": { "index": 28, "kind": "parameter", "displayName": "Postman Api Key Header", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "X-Api-Key", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The HTTP header used to send the Postman API key when fetching a collection." },
+ "sslContextParameters": { "index": 29, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Customize TLS parameters used by the component. If not set defaults to the TLS parameters set in the Camel context. These parameters are used both when fetching a collection from the Postman cloud and by the delegate producer." },
+ "useGlobalSslContextParameters": { "index": 30, "kind": "parameter", "displayName": "Use Global Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "Enable usage of global SSL context parameters." },
+ "postmanApiUrl": { "index": 31, "kind": "parameter", "displayName": "Postman Api Url", "group": "security (advanced)", "label": "security,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "defaultValue": "https:\/\/api.getpostman.com", "configurationClass": "org.apache.camel.component.rest.postman.RestPostmanConfiguration", "configurationField": "configuration", "description": "The base URL of the Postman API used to fetch collections. Must use https, except for localhost, because plain http would send the Postman API key in clear text." }
+ }
+}
diff --git a/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/component.properties b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/component.properties
new file mode 100644
index 0000000000000..156afbeb817a1
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/component.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+components=rest-postman
+groupId=org.apache.camel
+artifactId=camel-rest-postman
+version=4.22.0-SNAPSHOT
+projectName=Camel :: REST Postman
+projectDescription=Camel REST support using Postman Collections
diff --git a/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/component/rest-postman b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/component/rest-postman
new file mode 100644
index 0000000000000..d06dc4a0ec6b0
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/component/rest-postman
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.rest.postman.RestPostmanComponent
diff --git a/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.rest.postman.RestPostmanConfiguration b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.rest.postman.RestPostmanConfiguration
new file mode 100644
index 0000000000000..e048cf549a337
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.component.rest.postman.RestPostmanConfiguration
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.rest.postman.RestPostmanConfigurationConfigurer
diff --git a/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/rest-postman-component b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/rest-postman-component
new file mode 100644
index 0000000000000..c3187f73a0003
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/rest-postman-component
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.rest.postman.RestPostmanComponentConfigurer
diff --git a/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/rest-postman-endpoint b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/rest-postman-endpoint
new file mode 100644
index 0000000000000..82d0e82a66b44
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/configurer/rest-postman-endpoint
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.rest.postman.RestPostmanEndpointConfigurer
diff --git a/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/urifactory/rest-postman-endpoint b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/urifactory/rest-postman-endpoint
new file mode 100644
index 0000000000000..d1050d07eef8c
--- /dev/null
+++ b/components/camel-rest-postman/src/generated/resources/META-INF/services/org/apache/camel/urifactory/rest-postman-endpoint
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.component.rest.postman.RestPostmanEndpointUriFactory
diff --git a/components/camel-rest-postman/src/main/docs/rest-postman-component.adoc b/components/camel-rest-postman/src/main/docs/rest-postman-component.adoc
new file mode 100644
index 0000000000000..43513d2229ce3
--- /dev/null
+++ b/components/camel-rest-postman/src/main/docs/rest-postman-component.adoc
@@ -0,0 +1,232 @@
+= REST Postman Component
+:doctitle: REST Postman
+:shortname: rest-postman
+:artifactid: camel-rest-postman
+:description: To call and expose REST services using a Postman Collection as contract.
+:since: 4.22
+:supportlevel: Preview
+:tabs-sync-option:
+:component-header: Both producer and consumer are supported
+
+*Since Camel {since}*
+
+*{component-header}*
+
+The REST Postman component configures rest producers and contract-first rest consumers from a
+https://learning.postman.com/docs/collections/collections-overview/[Postman Collection], and delegates to a component
+implementing the _RestProducerFactory_ interface. Currently, known working components are:
+
+* xref:http-component.adoc[http]
+* xref:netty-http-component.adoc[netty-http]
+* xref:undertow-component.adoc[undertow]
+* xref:vertx-http-component.adoc[vertx-http]
+
+It is the Postman equivalent of xref:rest-openapi-component.adoc[rest-openapi]. Use it when a Postman Collection is
+the description of the API you have, rather than an OpenAPI specification.
+
+IMPORTANT: Only the Postman Collection Format v2.1 is supported.
+
+Maven users will need to add the following dependency to their `pom.xml` for this component:
+
+[source,xml]
+----
+
+ org.apache.camel
+ camel-rest-postman
+ x.x.x
+
+
+----
+
+== URI format
+
+----
+rest-postman:[collectionSource#]requestId
+----
+
+`collectionSource` is either a resource URI of a Collection v2.1 JSON document (`classpath:`, `file:` or `http:`), or
+the uid of a collection to fetch from the Postman cloud. It defaults to `postman-collection.json` on the classpath.
+
+`requestId` selects what to invoke:
+
+[cols="1,3", options="header"]
+|===
+| Fragment | Selects
+| `getPetById` | the single request whose name slugifies to `getPetById`
+| `pets/addPet` | the request `Add Pet` inside the folder `Pets`, used when a name is not unique
+| `3f2504e0-4f89-11d3-9a0c-0305e82c3301` | the request with that `id`, which only collections fetched from the cloud carry
+| `pets` | every request in the folder `Pets`
+| `pets/` | the folder `Pets`, forced, for when a request and a folder share a name
+| _omitted_ | every request in the collection
+|===
+
+This component's endpoint URI is lenient, which means that in addition to message headers you can specify a request's
+parameters as endpoint parameters. These will be constant for all subsequent invocations, so it makes sense to use
+this feature only for parameters that are indeed constant for all invocations.
+
+// component options: START
+include::partial$component-configure-options.adoc[]
+include::partial$component-endpoint-options.adoc[]
+include::partial$component-endpoint-headers.adoc[]
+// component options: END
+
+== Usage
+
+=== Identifying requests
+
+A Postman item has a human readable name rather than an operation id, so this component slugifies it:
+`Get Pet By Id` becomes `getPetById`. When two requests slugify to the same thing, both are addressed by their folder
+qualified id instead, such as `pets/get` and `users/get`, and using the bare `get` is an error that lists the
+alternatives.
+
+`item.id` is also accepted when the collection records one. Note that it is optional in the v2.1 schema, and Postman's
+exporter strips auto-generated item ids, so an exported `collection.json` usually has none. Collections fetched from
+the Postman cloud do.
+
+IMPORTANT: Because the common case is to address a request by its slugified name, renaming a request in the Postman
+UI changes its id and will break routes bound to it.
+
+=== Invoking a single request
+
+[source,java]
+----
+from("direct:start")
+ .to("rest-postman:petstore.json#getPetById");
+----
+
+The message body and headers of the exchange are what is sent. The collection supplies the method, the URL, and any
+headers the message does not already carry; the body written in the collection is treated as sample data and is not
+sent.
+
+Path parameters written as `:petId` become `+{petId}+` placeholders resolved per exchange from the message header of the
+same name, falling back to the value declared in `url.variable`. Query parameters are bound to message headers in the
+same way and are dropped when unresolved. Set `queryParameterMode=literal` to send the values written in the
+collection instead.
+
+=== Running a folder or a whole collection
+
+Naming a folder, or naming nothing at all, runs every request in turn, in the manner of Postman's collection runner:
+
+[source,java]
+----
+from("direct:smokeTest")
+ .to("rest-postman:petstore.json#pets") // every request in the Pets folder
+ .to("rest-postman:petstore.json"); // every request in the collection
+----
+
+Because one exchange body cannot stand in for many different requests, each request sends the body and headers written
+in the collection. `raw`, `graphql` and `urlencoded` bodies are reconstructed; `formdata` and `file` bodies cannot be,
+and are skipped with a warning. The `file` body mode is never read from disk, as it records a path on the machine of
+whoever authored the collection.
+
+The message body becomes a `List` of `PostmanRunResult`, one per request, each carrying the request id, method, URI,
+status code, response body, headers and any failure. The headers `CamelRestPostmanRequestCount` and
+`CamelRestPostmanFailedCount` summarise the run.
+
+By default the run stops and fails on the first request that fails. Set `runFailFast=false` to attempt every request
+and record the failures in their results instead:
+
+[source,java]
+----
+from("timer:smoke?period=60000")
+ .to("rest-postman:petstore.json?runFailFast=false")
+ .split(body())
+ .filter(simple("${body.success} == false"))
+ .to("log:failures");
+----
+
+=== Contract-first consumer
+
+Pointing a route's `from` at a collection serves its requests over HTTP, dispatching each one to a route consuming
+from `direct:`:
+
+[source,java]
+----
+from("rest-postman:petstore.json")
+ .to("direct:dummy");
+
+from("direct:getPetById")
+ .setBody(constant("{ \"id\": 42 }"));
+----
+
+Use a folder id to serve only part of a collection, and `requestFilter` to include or exclude requests by Ant-style
+patterns over their folder qualified ids.
+
+Because a collection does not describe a base path, the split between base path and route path is inferred from what
+`+{{baseUrl}}+` expands to. Set `basePath` explicitly to control the context path the consumer serves on.
+
+`missingRequest` decides what happens when a request has no corresponding route: `fail` (the default) refuses to
+start, `ignore` warns, and `mock` returns a mocked response. Mock responses are taken from the collection's own saved
+example responses where there are any, which is the one place a collection is richer than an OpenAPI specification,
+and fall back to files matched by `mockIncludePattern`.
+
+If two requests share an HTTP method and path, which is common when a collection keeps a success and an error variant
+of the same call, the consumer fails at startup rather than letting one silently shadow the other. Use `requestFilter`
+to choose between them.
+
+=== Variables
+
+`+{{variable}}+` placeholders are resolved from the collection's own `variable` arrays, with folder scopes overriding
+the collection scope, then from the endpoint's `variables` option, then from Camel property placeholders:
+
+[source,java]
+----
+from("direct:start")
+ .to("rest-postman:petstore.json#getPetById?variable.baseUrl=https://staging.example.com/v3");
+----
+
+Postman environment files are not supported. Unresolved placeholders are left as they are unless
+`failOnUnresolvedVariable=true`.
+
+A placeholder name written in the `prefix:value` form of a Camel property placeholder function -- `+{{env:HOME}}+`,
+`+{{sys:user.home}}+`, `+{{bean:foo}}+` and the vault functions among them -- is deliberately *not* resolved from
+Camel properties. A collection is route-author configuration, but a cloud-hosted one is editable by anyone with
+access to the Postman workspace, and resolving those would let its content pull an environment variable into an
+outgoing request. Supply such values through the `variables` option instead.
+
+NOTE: Pre-request and test scripts in the collection's `event` blocks are never parsed or executed.
+
+== Security
+
+=== Two different credentials
+
+There are two unrelated credentials in play, and the option names keep them apart:
+
+`postmanApiKey`:: authenticates against *Postman itself*, in order to download a collection from the Postman cloud. It
+is sent only to `postmanApiUrl`, and never to the API that the collection describes.
+
+the collection's own `auth` block:: authenticates against *the API the collection describes*. It is governed by the
+`collectionAuth` option.
+
+=== Fetching a collection from the Postman cloud
+
+[source,java]
+----
+from("direct:start")
+ .to("rest-postman:12ece9e1-2abf-4edc-8e34-de66e74114d2#getPetById?postmanApiKey=PMAK-xxxx");
+----
+
+Prefer resolving the key from a vault or a property placeholder over writing it in the URI. Redirects from
+`postmanApiUrl` are rejected rather than followed, because following one would send the key to the redirect target,
+and `postmanApiUrl` must use HTTPS unless it names a loopback host.
+
+=== Applying the collection's auth block
+
+`collectionAuth` defaults to `ignore`: the block is not applied, and a warning names the type that was found. This is
+deliberate, because the values in a collection's auth block are usually unresolved `+{{placeholders}}+`, and silently
+attaching a credential found in a configuration file to outbound requests is surprising.
+
+Set `collectionAuth=header` to apply it. The `basic`, `bearer` and `apikey` types are reproduced as a static header or
+query parameter. The types that require per-request signing or a token exchange -- `awsv4`, `digest`, `hawk`,
+`edgegrid`, `ntlm`, `oauth1` and `oauth2` -- fail at startup rather than silently sending no credential; configure
+those on the delegate HTTP component instead. `collectionAuth=fail` rejects any auth block at all.
+
+On the consumer side the collection's auth block describes what a client must present, and is *not* enforced. Use
+`oauthProfile`, or the delegate consumer component's own authentication, for that.
+
+=== Serving the collection document
+
+When `apiContextPath` is set, the collection is served on that path with every `auth` block removed and the value of
+every variable of type `secret` replaced. This redaction is unconditional.
+
+include::spring-boot:partial$starter.adoc[]
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/DefaultRestPostmanProcessorStrategy.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/DefaultRestPostmanProcessorStrategy.java
new file mode 100644
index 0000000000000..77f0b33a230e5
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/DefaultRestPostmanProcessorStrategy.java
@@ -0,0 +1,428 @@
+/*
+ * 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.component.rest.postman;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.camel.AsyncCallback;
+import org.apache.camel.AsyncProducer;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Endpoint;
+import org.apache.camel.Exchange;
+import org.apache.camel.NonManagedService;
+import org.apache.camel.Route;
+import org.apache.camel.component.platform.http.PlatformHttpComponent;
+import org.apache.camel.component.platform.http.spi.PlatformHttpConsumerAware;
+import org.apache.camel.component.rest.postman.model.PostmanKeyValue;
+import org.apache.camel.component.rest.postman.model.PostmanResponse;
+import org.apache.camel.component.rest.postman.support.PostmanRequestBinding;
+import org.apache.camel.spi.PackageScanResourceResolver;
+import org.apache.camel.spi.ProducerCache;
+import org.apache.camel.spi.Resource;
+import org.apache.camel.support.ExchangeHelper;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.cache.DefaultProducerCache;
+import org.apache.camel.support.processor.RestBindingAdvice;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.json.JsonObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default {@link RestPostmanProcessorStrategy}, which links each request of the collection to a route consuming from
+ * {@code direct:}.
+ */
+public class DefaultRestPostmanProcessorStrategy extends ServiceSupport
+ implements RestPostmanProcessorStrategy, CamelContextAware, NonManagedService {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultRestPostmanProcessorStrategy.class);
+
+ private static final String BODY_VERBS = "DELETE,PUT,POST,PATCH";
+
+ private CamelContext camelContext;
+ private ProducerCache producerCache;
+ private String component = "direct";
+ private String missingRequest;
+ private String mockIncludePattern;
+ private final List uris = new ArrayList<>();
+
+ @Override
+ public String resolveDispatchId(PostmanRequestBinding binding) {
+ Set existing = existingDirectEndpoints();
+ // more than one spelling is accepted so that both an exported collection, routed by slug, and a
+ // cloud-fetched one, routed by request id, work without the author having to know which they have
+ for (String candidate : candidateIds(binding)) {
+ if (existing.contains(component + "://" + candidate)) {
+ return candidate;
+ }
+ }
+ return binding.id();
+ }
+
+ private static List candidateIds(PostmanRequestBinding binding) {
+ List candidates = new ArrayList<>(3);
+ candidates.add(binding.id());
+ String qualified = binding.item().getQualifiedSlug();
+ if (!candidates.contains(qualified)) {
+ candidates.add(qualified);
+ }
+ String id = binding.item().getId();
+ if (id != null && !candidates.contains(id)) {
+ candidates.add(id);
+ }
+ return candidates;
+ }
+
+ /**
+ * The base URIs of every route currently in the context.
+ *
+ * Comparing base URIs is used rather than {@code hasEndpoint} because looking an endpoint up would create it.
+ */
+ private Set existingDirectEndpoints() {
+ Set answer = new LinkedHashSet<>();
+ for (Route route : camelContext.getRoutes()) {
+ answer.add(route.getEndpoint().getEndpointBaseUri());
+ }
+ return answer;
+ }
+
+ @Override
+ public void validateCollection(
+ List bindings, String basePath, PlatformHttpConsumerAware platformHttpConsumer)
+ throws Exception {
+
+ failOnShadowedRequests(bindings);
+
+ Set existing = existingDirectEndpoints();
+ List missing = new ArrayList<>();
+ for (PostmanRequestBinding binding : bindings) {
+ boolean found = candidateIds(binding).stream()
+ .anyMatch(candidate -> existing.contains(component + "://" + candidate));
+ if (!found) {
+ missing.add(component + ":" + binding.id());
+ }
+ }
+
+ if (!missing.isEmpty()) {
+ String message = String.format(
+ "Postman collection has %d request(s) not mapped to a corresponding route:%n\t%s",
+ missing.size(), String.join("\n\t", missing.stream().sorted().toList()));
+ if ("fail".equalsIgnoreCase(missingRequest)) {
+ throw new IllegalArgumentException(message);
+ } else if ("ignore".equalsIgnoreCase(missingRequest)) {
+ LOG.warn("{}\nThis validation error is ignored.", message);
+ } else {
+ LOG.debug("{}\nThis validation error is ignored (a mocked response will be returned).", message);
+ }
+ }
+
+ registerHttpEndpoints(bindings, basePath, platformHttpConsumer);
+ }
+
+ /**
+ * Rejects a collection where two requests share a verb and path.
+ *
+ * This is common in real collections, which often keep a success and an error variant of the same call. The matcher
+ * would pick one of them non-deterministically, so failing loudly at startup is better than silently shadowing a
+ * route.
+ */
+ private void failOnShadowedRequests(List bindings) {
+ Map> byRoute = new LinkedHashMap<>();
+ for (PostmanRequestBinding binding : bindings) {
+ byRoute.computeIfAbsent(binding.method() + " " + binding.fullPath(), k -> new ArrayList<>()).add(binding);
+ }
+ List clashes = byRoute.entrySet().stream()
+ .filter(e -> e.getValue().size() > 1)
+ .map(e -> e.getKey() + " is declared by "
+ + e.getValue().stream().map(PostmanRequestBinding::id).collect(Collectors.joining(", ")))
+ .toList();
+ if (!clashes.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Postman collection has requests that would shadow each other because they share an HTTP method"
+ + " and path:\n\t" + String.join("\n\t", clashes)
+ + "\nUse the requestFilter option to select which of them to serve.");
+ }
+ }
+
+ private void registerHttpEndpoints(
+ List bindings, String basePath, PlatformHttpConsumerAware platformHttpConsumer) {
+ if (platformHttpConsumer == null) {
+ return;
+ }
+ // hasComponent rather than getComponent: looking it up would auto-create the component, which fails
+ // outside a runtime that provides an HTTP engine
+ if (!(camelContext.hasComponent("platform-http") instanceof PlatformHttpComponent phc)) {
+ return;
+ }
+ String prefix = basePath == null || basePath.isEmpty() || "/".equals(basePath) ? "" : basePath;
+
+ Map> verbsByUri = new LinkedHashMap<>();
+ Map consumesByUri = new LinkedHashMap<>();
+ Map producesByUri = new LinkedHashMap<>();
+ for (PostmanRequestBinding binding : bindings) {
+ String uri = prefix + binding.uriTemplate();
+ verbsByUri.computeIfAbsent(uri, k -> new LinkedHashSet<>()).add(binding.method());
+ if (binding.produces() != null) {
+ consumesByUri.putIfAbsent(uri, binding.produces());
+ }
+ if (binding.consumes() != null) {
+ producesByUri.putIfAbsent(uri, binding.consumes());
+ }
+ }
+ verbsByUri.forEach((uri, verbs) -> {
+ phc.addHttpEndpoint(uri, String.join(",", verbs.stream().sorted().toList()),
+ consumesByUri.get(uri), producesByUri.get(uri), platformHttpConsumer.getPlatformHttpConsumer());
+ uris.add(uri);
+ });
+ }
+
+ @Override
+ public boolean process(
+ PostmanRequestBinding binding, String dispatchId, String verb, String path,
+ RestBindingAdvice advice, Exchange exchange, AsyncCallback callback) {
+
+ exchange.getMessage().setHeader(RestPostmanConstants.REQUEST_ID, binding.id());
+ exchange.getMessage().setHeader(RestPostmanConstants.REQUEST_NAME, binding.item().getName());
+
+ if ("mock".equalsIgnoreCase(missingRequest) || "ignore".equalsIgnoreCase(missingRequest)) {
+ Endpoint existing = camelContext.hasEndpoint(component + ":" + dispatchId);
+ if (existing == null) {
+ try {
+ var requestError = advice.doClientRequestValidation(exchange);
+ if (requestError != null) {
+ exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, requestError.statusCode());
+ exchange.getMessage().setBody(requestError.body());
+ exchange.setRouteStop(true);
+ } else if ("mock".equalsIgnoreCase(missingRequest)) {
+ loadMockData(binding, path, exchange);
+ }
+ } catch (Exception e) {
+ exchange.setException(e);
+ }
+ callback.done(true);
+ return true;
+ }
+ }
+
+ Map state;
+ try {
+ state = advice.before(exchange);
+ } catch (Exception e) {
+ exchange.setException(e);
+ callback.done(true);
+ return true;
+ }
+
+ final Endpoint endpoint = camelContext.getEndpoint(component + ":" + dispatchId);
+ final AsyncProducer producer = producerCache.acquireProducer(endpoint);
+ return producer.process(exchange, doneSync -> {
+ try {
+ producerCache.releaseProducer(endpoint, producer);
+ advice.after(exchange, state);
+ } catch (Exception e) {
+ exchange.setException(e);
+ } finally {
+ callback.done(doneSync);
+ }
+ });
+ }
+
+ /**
+ * Produces a mock response.
+ *
+ * A saved example in the collection is preferred over a file on disk, because it is a real recorded response for
+ * exactly this request and needs no naming convention to find.
+ */
+ private void loadMockData(PostmanRequestBinding binding, String path, Exchange exchange) {
+ PostmanResponse example = pickSavedResponse(binding, exchange);
+ if (example != null) {
+ exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, example.getCode());
+ for (PostmanKeyValue header : example.getHeaders()) {
+ if (!header.disabled()) {
+ exchange.getMessage().setHeader(header.key(), header.value());
+ }
+ }
+ exchange.getMessage().setBody(example.getBody());
+ return;
+ }
+ loadMockDataFromFiles(path, exchange);
+ }
+
+ /**
+ * Chooses the saved example that best matches what the caller asked for: a successful one, preferring a content
+ * type the caller said it accepts.
+ */
+ private static PostmanResponse pickSavedResponse(PostmanRequestBinding binding, Exchange exchange) {
+ List saved = binding.item().getSavedResponses().stream()
+ .filter(PostmanResponse::isSuccess)
+ .toList();
+ if (saved.isEmpty()) {
+ return null;
+ }
+ String accept = exchange.getMessage().getHeader("Accept", String.class);
+ if (accept != null) {
+ for (PostmanResponse candidate : saved) {
+ String contentType = candidate.getContentType();
+ if (contentType != null && accept.contains(stripParameters(contentType))) {
+ return candidate;
+ }
+ }
+ }
+ return saved.get(0);
+ }
+
+ private static String stripParameters(String contentType) {
+ int semicolon = contentType.indexOf(';');
+ return semicolon > 0 ? contentType.substring(0, semicolon).trim() : contentType.trim();
+ }
+
+ private void loadMockDataFromFiles(String path, Exchange exchange) {
+ final PackageScanResourceResolver resolver = PluginHelper.getPackageScanResourceResolver(camelContext);
+ final String[] includes = mockIncludePattern != null ? mockIncludePattern.split(",") : null;
+ if (includes == null) {
+ return;
+ }
+
+ Collection accepted = new ArrayList<>();
+ for (String include : includes) {
+ try {
+ accepted.addAll(resolver.findResources(include));
+ } catch (Exception e) {
+ LOG.trace("Mock data directory {} cannot be scanned", include, e);
+ }
+ }
+
+ String contentType = ExchangeHelper.getContentType(exchange);
+ boolean json = contentType != null && contentType.contains("json");
+ boolean xml = contentType != null && contentType.contains("xml");
+
+ String target = FileUtil.stripFirstLeadingSeparator(path);
+ for (Resource resource : accepted) {
+ String location = FileUtil.stripExt(FileUtil.compactPath(resource.getLocation(), '/'));
+ String extension = FileUtil.onlyExt(resource.getLocation());
+ boolean matchExt = !json && !xml
+ || json && "json".equals(extension)
+ || xml && "xml".equals(extension);
+ if (location.endsWith(target) && matchExt) {
+ try (InputStream is = resource.getInputStream()) {
+ exchange.getMessage().setBody(IOHelper.loadText(is));
+ if ("json".equals(extension)) {
+ exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "application/json");
+ } else if ("xml".equals(extension)) {
+ exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "application/xml");
+ }
+ return;
+ } catch (Exception e) {
+ exchange.setException(e);
+ return;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean processCollectionDocument(JsonObject redactedDocument, Exchange exchange, AsyncCallback callback) {
+ try {
+ exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "application/json");
+ exchange.getMessage().setBody(redactedDocument.toJson());
+ } catch (Exception e) {
+ exchange.setException(e);
+ }
+ callback.done(true);
+ return true;
+ }
+
+ /**
+ * Whether the verb normally carries a request body, used to decide if one should be required.
+ */
+ static boolean expectsBody(String verb) {
+ return BODY_VERBS.contains(verb.toUpperCase(Locale.ROOT));
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ @Override
+ public String getMissingRequest() {
+ return missingRequest;
+ }
+
+ @Override
+ public void setMissingRequest(String missingRequest) {
+ this.missingRequest = missingRequest;
+ }
+
+ @Override
+ public String getMockIncludePattern() {
+ return mockIncludePattern;
+ }
+
+ @Override
+ public void setMockIncludePattern(String mockIncludePattern) {
+ this.mockIncludePattern = mockIncludePattern;
+ }
+
+ @Override
+ protected void doInit() throws Exception {
+ producerCache = new DefaultProducerCache(this, getCamelContext(), 1000);
+ ServiceHelper.initService(producerCache);
+
+ if (missingRequest == null) {
+ // in developer mode an unmapped request is far more likely to be work in progress than a mistake
+ boolean dev = "dev".equalsIgnoreCase(camelContext.getCamelContextExtension().getProfile());
+ missingRequest = dev ? "mock" : "fail";
+ }
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ ServiceHelper.startService(producerCache);
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ ServiceHelper.stopService(producerCache);
+
+ if (camelContext != null) {
+ PlatformHttpComponent phc = (PlatformHttpComponent) camelContext.hasComponent("platform-http");
+ if (phc != null) {
+ uris.forEach(phc::removeHttpEndpoint);
+ uris.clear();
+ }
+ }
+ }
+}
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/PostmanRunResult.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/PostmanRunResult.java
new file mode 100644
index 0000000000000..ed0afd316145b
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/PostmanRunResult.java
@@ -0,0 +1,60 @@
+/*
+ * 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.component.rest.postman;
+
+import java.util.Map;
+
+/**
+ * The outcome of one request when a folder or a whole collection is run.
+ *
+ * A list of these becomes the message body of the exchange, so a route can split over them, filter the failures, or
+ * report on them.
+ *
+ * @param requestId the id of the request within the collection
+ * @param name the request name as written in the collection
+ * @param folderPath the enclosing folders, separated by a slash, or {@code null} at the top level
+ * @param method the HTTP method used
+ * @param uri the URI called
+ * @param httpStatus the HTTP status code, or {@code null} when the call did not complete
+ * @param body the response body, or {@code null}
+ * @param headers the response headers, never {@code null}
+ * @param failure the failure message when the call did not succeed, otherwise {@code null}
+ */
+public record PostmanRunResult(
+ String requestId,
+ String name,
+ String folderPath,
+ String method,
+ String uri,
+ Integer httpStatus,
+ Object body,
+ Map headers,
+ String failure) {
+
+ /**
+ * Whether this request completed without an exception.
+ */
+ public boolean isSuccess() {
+ return failure == null;
+ }
+
+ @Override
+ public String toString() {
+ return "PostmanRunResult[" + requestId + " " + method + " " + uri
+ + (failure != null ? " FAILED: " + failure : " -> " + httpStatus) + "]";
+ }
+}
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanComponent.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanComponent.java
new file mode 100644
index 0000000000000..08e323bf3676c
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanComponent.java
@@ -0,0 +1,170 @@
+/*
+ * 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.component.rest.postman;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Endpoint;
+import org.apache.camel.SSLContextParametersAware;
+import org.apache.camel.component.rest.postman.collection.PostmanCollectionCache;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.Component;
+import org.apache.camel.support.DefaultComponent;
+import org.apache.camel.support.jsse.SSLContextParameters;
+import org.apache.camel.util.PropertiesHelper;
+
+/**
+ * A REST component backed by Postman Collections.
+ *
+ * Endpoints created by this component connect to the REST APIs described by a Postman Collection, either invoking one
+ * request of the collection, running a whole folder or collection in the manner of Postman's collection runner, or
+ * servicing the collection's requests as a contract-first HTTP server.
+ *
+ * The collection can also be fetched from the Postman cloud by its uid, which needs a Postman API key. Note that this
+ * key authenticates against Postman in order to download the collection; it is never sent to the API that the
+ * collection describes:
+ *
+ *
+ */
+@Component("rest-postman")
+public class RestPostmanComponent extends DefaultComponent implements SSLContextParametersAware {
+
+ @Metadata(description = "The Postman Collection to use, when it is not given on the endpoint. Either a resource"
+ + " URI of a Collection v2.1 JSON document (classpath:, file: or http:), or the uid of a"
+ + " collection to fetch from the Postman cloud.",
+ label = "common")
+ private String collectionSource;
+
+ @Metadata(label = "advanced")
+ private RestPostmanConfiguration configuration = new RestPostmanConfiguration();
+
+ @Metadata(description = "To use a custom strategy for how to service the requests of the collection.",
+ label = "consumer,advanced")
+ private RestPostmanProcessorStrategy restPostmanProcessorStrategy;
+
+ private final PostmanCollectionCache collectionCache = new PostmanCollectionCache();
+
+ public RestPostmanComponent() {
+ }
+
+ public RestPostmanComponent(final CamelContext context) {
+ super(context);
+ }
+
+ @Override
+ protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception {
+ // the endpoint is lenient, so anything left in parameters becomes a literal path or query value. The
+ // multi-value variable. prefix therefore has to be taken out explicitly, or every variable would also be
+ // sent as a bogus query parameter
+ Map variables = PropertiesHelper.extractProperties(parameters, "variable.");
+
+ RestPostmanEndpoint endpoint = new RestPostmanEndpoint(uri, remaining, this, parameters);
+ endpoint.setConfiguration(configuration.copy());
+ setProperties(endpoint, parameters);
+
+ if (!variables.isEmpty()) {
+ Map merged = new LinkedHashMap<>();
+ if (endpoint.getConfiguration().getVariables() != null) {
+ merged.putAll(endpoint.getConfiguration().getVariables());
+ }
+ merged.putAll(variables);
+ endpoint.getConfiguration().setVariables(merged);
+ }
+ return endpoint;
+ }
+
+ /**
+ * Creates the strategy an endpoint's consumer should use: the configured one when given, otherwise a fresh default
+ * per endpoint, since the strategy holds per-endpoint state.
+ */
+ RestPostmanProcessorStrategy createProcessorStrategy() {
+ if (restPostmanProcessorStrategy != null) {
+ return restPostmanProcessorStrategy;
+ }
+ DefaultRestPostmanProcessorStrategy strategy = new DefaultRestPostmanProcessorStrategy();
+ CamelContextAware.trySetCamelContext(strategy, getCamelContext());
+ strategy.setMissingRequest(configuration.getMissingRequest());
+ strategy.setMockIncludePattern(configuration.getMockIncludePattern());
+ return strategy;
+ }
+
+ PostmanCollectionCache getCollectionCache() {
+ return collectionCache;
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ super.doStop();
+ collectionCache.clear();
+ }
+
+ public String getCollectionSource() {
+ return collectionSource;
+ }
+
+ public void setCollectionSource(String collectionSource) {
+ this.collectionSource = collectionSource;
+ }
+
+ public RestPostmanConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * The shared configuration used as the template for every endpoint created by this component.
+ */
+ public void setConfiguration(RestPostmanConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+ public RestPostmanProcessorStrategy getRestPostmanProcessorStrategy() {
+ return restPostmanProcessorStrategy;
+ }
+
+ public void setRestPostmanProcessorStrategy(RestPostmanProcessorStrategy restPostmanProcessorStrategy) {
+ this.restPostmanProcessorStrategy = restPostmanProcessorStrategy;
+ }
+
+ @Override
+ public boolean isUseGlobalSslContextParameters() {
+ return configuration.isUseGlobalSslContextParameters();
+ }
+
+ @Override
+ public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) {
+ configuration.setUseGlobalSslContextParameters(useGlobalSslContextParameters);
+ }
+
+ public SSLContextParameters getSslContextParameters() {
+ return configuration.getSslContextParameters();
+ }
+
+ public void setSslContextParameters(SSLContextParameters sslContextParameters) {
+ configuration.setSslContextParameters(sslContextParameters);
+ }
+}
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConfiguration.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConfiguration.java
new file mode 100644
index 0000000000000..9b53c0dcdea05
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConfiguration.java
@@ -0,0 +1,424 @@
+/*
+ * 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.component.rest.postman;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriParams;
+import org.apache.camel.support.jsse.SSLContextParameters;
+
+/**
+ * Configuration shared by the {@code rest-postman} component and its endpoints.
+ *
+ * This class deliberately does not generate a {@code toString}, because it holds the Postman API key.
+ */
+@Configurer(extended = true)
+@UriParams
+public class RestPostmanConfiguration implements Cloneable {
+
+ public static final String DEFAULT_COLLECTION_SOURCE = "postman-collection.json";
+ public static final String DEFAULT_POSTMAN_API_URL = "https://api.getpostman.com";
+ public static final String DEFAULT_POSTMAN_API_KEY_HEADER = "X-Api-Key";
+ public static final String DEFAULT_BASE_PATH = "/";
+
+ @UriParam(label = "common", enums = "auto,resource,cloud", defaultValue = "auto")
+ @Metadata(description = "How to interpret collectionSource. With auto, a bare collection UUID or"
+ + " {ownerId}-{uuid} is fetched from the Postman cloud and anything else is resolved as a"
+ + " resource (classpath:, file:, http:). Use resource or cloud to decide explicitly.",
+ defaultValue = "auto")
+ private String collectionSourceType = "auto";
+
+ @UriParam(label = "common")
+ @Metadata(description = "API basePath, for example \"`/v2`\". Default is unset, if set overrides the value"
+ + " derived from the request URL in the collection.")
+ private String basePath = "";
+
+ @UriParam(label = "common", prefix = "variable.", multiValue = true)
+ @Metadata(description = "Values for the {{variable}} placeholders used in the collection. These override the"
+ + " variables declared by the collection and its folders.")
+ private Map variables;
+
+ @UriParam(label = "common,advanced")
+ @Metadata(description = "Whether to fail if a {{variable}} placeholder used by the selected request cannot be"
+ + " resolved. When false the placeholder is left as-is.")
+ private boolean failOnUnresolvedVariable;
+
+ @UriParam(label = "producer")
+ @Metadata(description = "Scheme hostname and port to direct the HTTP requests to in the form of"
+ + " `http[s]://hostname[:port]`. If set overrides any value derived from the collection.")
+ private String host;
+
+ @UriParam(label = "producer,advanced")
+ @Metadata(description = "Name of the Camel component that will perform the requests. The component must be"
+ + " present in Camel registry and it must implement RestProducerFactory service provider"
+ + " interface. If not set CLASSPATH is searched for single component that implements"
+ + " RestProducerFactory SPI.")
+ private String componentName;
+
+ @UriParam(label = "producer,advanced")
+ @Metadata(description = "What payload type this component is capable of consuming. This equates to the value of"
+ + " the `Accept` HTTP header. A Postman collection does not describe responses, so unlike"
+ + " an OpenAPI specification there is nothing to infer this from and it is unset by"
+ + " default.")
+ private String consumes;
+
+ @UriParam(label = "producer,advanced")
+ @Metadata(description = "What payload type this component is producing. This equates to the value of the"
+ + " `Content-Type` HTTP header. If not set it is inferred from the body mode of the"
+ + " request in the collection.")
+ private String produces;
+
+ @UriParam(label = "producer,advanced", enums = "placeholder,literal", defaultValue = "placeholder")
+ @Metadata(description = "How to treat the query parameters declared in the collection. With placeholder the"
+ + " parameter names are bound to message headers and the values in the collection are"
+ + " ignored as sample data. With literal the values in the collection are sent as-is.",
+ defaultValue = "placeholder")
+ private String queryParameterMode = "placeholder";
+
+ @UriParam(label = "producer", defaultValue = "true")
+ @Metadata(description = "When the endpoint runs more than one request, that is when it selects a folder or the"
+ + " whole collection, whether to stop and fail on the first request that fails. When"
+ + " false every request is attempted and the failure is recorded in its result.",
+ defaultValue = "true")
+ private boolean runFailFast = true;
+
+ @UriParam(label = "consumer,advanced")
+ @Metadata(description = "Name of the Camel component that will service the requests. The component must be"
+ + " present in Camel registry and it must be able to service contract-first REST"
+ + " consumers, as platform-http does. If not set CLASSPATH is searched for a single"
+ + " component with that capability.")
+ private String consumerComponentName;
+
+ @UriParam(label = "consumer", enums = "fail,ignore,mock", defaultValue = "fail")
+ @Metadata(description = "Whether the consumer should fail, ignore or return a mock response for requests in the"
+ + " collection that are not mapped to a corresponding route.",
+ defaultValue = "fail")
+ private String missingRequest;
+
+ @UriParam(label = "consumer,advanced", defaultValue = "classpath:camel-mock/**")
+ @Metadata(description = "Used for inclusive filtering of mock data from directories. The pattern is using"
+ + " Ant-path style pattern. Multiple patterns can be specified separated by comma."
+ + " Saved example responses in the collection are preferred over these files.",
+ defaultValue = "classpath:camel-mock/**")
+ private String mockIncludePattern = "classpath:camel-mock/**";
+
+ @UriParam(label = "consumer")
+ @Metadata(description = "Whether to enable validation of the client request. A Postman collection has no schemas,"
+ + " so this is a best-effort check of required headers, query parameters and body"
+ + " presence only.")
+ private boolean clientRequestValidation;
+
+ @UriParam(label = "consumer")
+ @Metadata(description = "Sets the context-path to use for servicing the Postman collection document. The document"
+ + " is served with all auth blocks and all secret variables removed.")
+ private String apiContextPath;
+
+ @UriParam(label = "consumer,advanced")
+ @Metadata(description = "Filters which requests of the collection are used, as comma separated Ant-style patterns"
+ + " matched against the folder qualified request id. Prefix a pattern with ! to exclude.")
+ private String requestFilter;
+
+ @UriParam(label = "consumer,security", displayName = "OAuth Profile")
+ @Metadata(description = "The OAuth profile to use for authenticating the incoming requests. The profile is"
+ + " enforced by the consumer component servicing the requests.")
+ private String oauthProfile;
+
+ @UriParam(label = "security", security = "secret")
+ @Metadata(description = "The Postman API key used to fetch the collection from the Postman cloud. This"
+ + " credential authenticates against Postman itself and is never sent to the API the"
+ + " collection describes.",
+ security = "secret")
+ private String postmanApiKey;
+
+ @UriParam(label = "security", defaultValue = DEFAULT_POSTMAN_API_KEY_HEADER)
+ @Metadata(description = "The HTTP header used to send the Postman API key when fetching a collection.",
+ defaultValue = DEFAULT_POSTMAN_API_KEY_HEADER)
+ private String postmanApiKeyHeader = DEFAULT_POSTMAN_API_KEY_HEADER;
+
+ @UriParam(label = "security,advanced", defaultValue = DEFAULT_POSTMAN_API_URL)
+ @Metadata(description = "The base URL of the Postman API used to fetch collections. Must use https, except for"
+ + " localhost, because plain http would send the Postman API key in clear text.",
+ defaultValue = DEFAULT_POSTMAN_API_URL)
+ private String postmanApiUrl = DEFAULT_POSTMAN_API_URL;
+
+ @UriParam(label = "security", enums = "ignore,header,fail", defaultValue = "ignore")
+ @Metadata(description = "What to do with the auth block the collection declares for the target API. With ignore"
+ + " the block is not applied, and a warning names the type that was found. With header"
+ + " the basic, bearer and apikey types are applied as a static header or query parameter,"
+ + " and any other type fails at startup rather than silently sending no credential. With"
+ + " fail any auth block other than noauth is rejected.",
+ defaultValue = "ignore")
+ private String collectionAuth = "ignore";
+
+ @UriParam(label = "advanced", defaultValue = "15000")
+ @Metadata(description = "Connection timeout in milliseconds when fetching a collection from the Postman cloud.",
+ defaultValue = "15000")
+ private long connectTimeout = 15000;
+
+ @UriParam(label = "advanced", defaultValue = "30000")
+ @Metadata(description = "Request timeout in milliseconds when fetching a collection from the Postman cloud.",
+ defaultValue = "30000")
+ private long requestTimeout = 30000;
+
+ @UriParam(label = "advanced", defaultValue = "-1")
+ @Metadata(description = "How long a loaded collection is cached, in milliseconds. Use -1 to cache for the"
+ + " lifetime of the component.",
+ defaultValue = "-1")
+ private long collectionCacheTtl = -1;
+
+ @UriParam(label = "security")
+ @Metadata(description = "Customize TLS parameters used by the component. If not set defaults to the TLS"
+ + " parameters set in the Camel context. These parameters are used both when fetching a"
+ + " collection from the Postman cloud and by the delegate producer.")
+ private SSLContextParameters sslContextParameters;
+
+ @UriParam(label = "security", defaultValue = "false")
+ @Metadata(description = "Enable usage of global SSL context parameters.", defaultValue = "false")
+ private boolean useGlobalSslContextParameters;
+
+ public String getCollectionSourceType() {
+ return collectionSourceType;
+ }
+
+ public void setCollectionSourceType(String collectionSourceType) {
+ this.collectionSourceType = collectionSourceType;
+ }
+
+ public String getBasePath() {
+ return basePath;
+ }
+
+ public void setBasePath(String basePath) {
+ this.basePath = basePath;
+ }
+
+ public Map getVariables() {
+ return variables;
+ }
+
+ public void setVariables(Map variables) {
+ this.variables = variables;
+ }
+
+ public boolean isFailOnUnresolvedVariable() {
+ return failOnUnresolvedVariable;
+ }
+
+ public void setFailOnUnresolvedVariable(boolean failOnUnresolvedVariable) {
+ this.failOnUnresolvedVariable = failOnUnresolvedVariable;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = RestPostmanHelper.isHostParam(host);
+ }
+
+ public String getComponentName() {
+ return componentName;
+ }
+
+ public void setComponentName(String componentName) {
+ this.componentName = componentName;
+ }
+
+ public String getConsumes() {
+ return consumes;
+ }
+
+ public void setConsumes(String consumes) {
+ this.consumes = RestPostmanHelper.isMediaRange(consumes, "consumes");
+ }
+
+ public String getProduces() {
+ return produces;
+ }
+
+ public void setProduces(String produces) {
+ this.produces = RestPostmanHelper.isMediaRange(produces, "produces");
+ }
+
+ public String getQueryParameterMode() {
+ return queryParameterMode;
+ }
+
+ public void setQueryParameterMode(String queryParameterMode) {
+ this.queryParameterMode = queryParameterMode;
+ }
+
+ public boolean isRunFailFast() {
+ return runFailFast;
+ }
+
+ public void setRunFailFast(boolean runFailFast) {
+ this.runFailFast = runFailFast;
+ }
+
+ public String getConsumerComponentName() {
+ return consumerComponentName;
+ }
+
+ public void setConsumerComponentName(String consumerComponentName) {
+ this.consumerComponentName = consumerComponentName;
+ }
+
+ public String getMissingRequest() {
+ return missingRequest;
+ }
+
+ public void setMissingRequest(String missingRequest) {
+ this.missingRequest = missingRequest;
+ }
+
+ public String getMockIncludePattern() {
+ return mockIncludePattern;
+ }
+
+ public void setMockIncludePattern(String mockIncludePattern) {
+ this.mockIncludePattern = mockIncludePattern;
+ }
+
+ public boolean isClientRequestValidation() {
+ return clientRequestValidation;
+ }
+
+ public void setClientRequestValidation(boolean clientRequestValidation) {
+ this.clientRequestValidation = clientRequestValidation;
+ }
+
+ public String getApiContextPath() {
+ return apiContextPath;
+ }
+
+ public void setApiContextPath(String apiContextPath) {
+ this.apiContextPath = apiContextPath;
+ }
+
+ public String getRequestFilter() {
+ return requestFilter;
+ }
+
+ public void setRequestFilter(String requestFilter) {
+ this.requestFilter = requestFilter;
+ }
+
+ public String getOauthProfile() {
+ return oauthProfile;
+ }
+
+ public void setOauthProfile(String oauthProfile) {
+ this.oauthProfile = oauthProfile;
+ }
+
+ public String getPostmanApiKey() {
+ return postmanApiKey;
+ }
+
+ public void setPostmanApiKey(String postmanApiKey) {
+ this.postmanApiKey = postmanApiKey;
+ }
+
+ public String getPostmanApiKeyHeader() {
+ return postmanApiKeyHeader;
+ }
+
+ public void setPostmanApiKeyHeader(String postmanApiKeyHeader) {
+ this.postmanApiKeyHeader = postmanApiKeyHeader;
+ }
+
+ public String getPostmanApiUrl() {
+ return postmanApiUrl;
+ }
+
+ public void setPostmanApiUrl(String postmanApiUrl) {
+ this.postmanApiUrl = postmanApiUrl;
+ }
+
+ public String getCollectionAuth() {
+ return collectionAuth;
+ }
+
+ public void setCollectionAuth(String collectionAuth) {
+ this.collectionAuth = collectionAuth;
+ }
+
+ public long getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ public void setConnectTimeout(long connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ }
+
+ public long getRequestTimeout() {
+ return requestTimeout;
+ }
+
+ public void setRequestTimeout(long requestTimeout) {
+ this.requestTimeout = requestTimeout;
+ }
+
+ public long getCollectionCacheTtl() {
+ return collectionCacheTtl;
+ }
+
+ public void setCollectionCacheTtl(long collectionCacheTtl) {
+ this.collectionCacheTtl = collectionCacheTtl;
+ }
+
+ public SSLContextParameters getSslContextParameters() {
+ return sslContextParameters;
+ }
+
+ public void setSslContextParameters(SSLContextParameters sslContextParameters) {
+ this.sslContextParameters = sslContextParameters;
+ }
+
+ public boolean isUseGlobalSslContextParameters() {
+ return useGlobalSslContextParameters;
+ }
+
+ public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) {
+ this.useGlobalSslContextParameters = useGlobalSslContextParameters;
+ }
+
+ /**
+ * The endpoint level variable overrides, as plain strings.
+ */
+ public Map variablesAsStrings() {
+ Map answer = new LinkedHashMap<>();
+ if (variables != null) {
+ variables.forEach((key, value) -> answer.put(key, value != null ? value.toString() : ""));
+ }
+ return answer;
+ }
+
+ public RestPostmanConfiguration copy() {
+ try {
+ return (RestPostmanConfiguration) clone();
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeCamelException(e);
+ }
+ }
+}
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConstants.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConstants.java
new file mode 100644
index 0000000000000..04b647939f08e
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConstants.java
@@ -0,0 +1,47 @@
+/*
+ * 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.component.rest.postman;
+
+import org.apache.camel.spi.Metadata;
+
+/**
+ * Headers set by the {@code rest-postman} component.
+ */
+public final class RestPostmanConstants {
+
+ @Metadata(description = "The id of the Postman request being invoked or serviced.", javaType = "String")
+ public static final String REQUEST_ID = "CamelRestPostmanRequestId";
+
+ @Metadata(description = "The name of the Postman request, as written in the collection.", javaType = "String")
+ public static final String REQUEST_NAME = "CamelRestPostmanRequestName";
+
+ @Metadata(description = "The folder path of the Postman request, with folders separated by a slash.",
+ javaType = "String")
+ public static final String FOLDER_PATH = "CamelRestPostmanFolderPath";
+
+ @Metadata(description = "The number of requests executed when running a folder or a whole collection.",
+ javaType = "Integer")
+ public static final String REQUEST_COUNT = "CamelRestPostmanRequestCount";
+
+ @Metadata(description = "The number of requests that failed when running a folder or a whole collection with"
+ + " runFailFast disabled.",
+ javaType = "Integer")
+ public static final String FAILED_COUNT = "CamelRestPostmanFailedCount";
+
+ private RestPostmanConstants() {
+ }
+}
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConsumerPath.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConsumerPath.java
new file mode 100644
index 0000000000000..2c4a1139fc044
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanConsumerPath.java
@@ -0,0 +1,73 @@
+/*
+ * 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.component.rest.postman;
+
+import org.apache.camel.component.rest.postman.support.PostmanRequestBinding;
+import org.apache.camel.support.RestConsumerContextPathMatcher;
+import org.apache.camel.support.processor.RestBindingAdvice;
+
+/**
+ * One servable request of the collection, as the context path matcher sees it.
+ */
+class RestPostmanConsumerPath implements RestConsumerContextPathMatcher.ConsumerPath {
+
+ private final String verb;
+ private final String path;
+ private final PostmanRequestBinding consumer;
+ private final RestBindingAdvice binding;
+ private final String dispatchId;
+
+ RestPostmanConsumerPath(String verb, String path, PostmanRequestBinding consumer, RestBindingAdvice binding,
+ String dispatchId) {
+ this.verb = verb;
+ this.path = path;
+ this.consumer = consumer;
+ this.binding = binding;
+ this.dispatchId = dispatchId;
+ }
+
+ @Override
+ public String getRestrictMethod() {
+ return verb;
+ }
+
+ @Override
+ public String getConsumerPath() {
+ return path;
+ }
+
+ @Override
+ public PostmanRequestBinding getConsumer() {
+ return consumer;
+ }
+
+ @Override
+ public boolean isMatchOnUriPrefix() {
+ return false;
+ }
+
+ public RestBindingAdvice getBinding() {
+ return binding;
+ }
+
+ /**
+ * The {@code direct} endpoint name this request routes to, resolved at startup against the routes that exist.
+ */
+ public String getDispatchId() {
+ return dispatchId;
+ }
+}
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanEndpoint.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanEndpoint.java
new file mode 100644
index 0000000000000..388aa8364120d
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanEndpoint.java
@@ -0,0 +1,539 @@
+/*
+ * 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.component.rest.postman;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Category;
+import org.apache.camel.Component;
+import org.apache.camel.Consumer;
+import org.apache.camel.Endpoint;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.NoSuchBeanException;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.platform.http.spi.PlatformHttpConsumerAware;
+import org.apache.camel.component.rest.postman.collection.PostmanCloudClient;
+import org.apache.camel.component.rest.postman.collection.PostmanCollectionLoader;
+import org.apache.camel.component.rest.postman.model.PostmanCollection;
+import org.apache.camel.component.rest.postman.model.PostmanItem;
+import org.apache.camel.component.rest.postman.support.PostmanRedactor;
+import org.apache.camel.component.rest.postman.support.PostmanRequestBinding;
+import org.apache.camel.component.rest.postman.support.PostmanRequestIndex;
+import org.apache.camel.component.rest.postman.support.PostmanRequestMapper;
+import org.apache.camel.spi.InternalProcessor;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.spi.RestOpenApiConsumerFactory;
+import org.apache.camel.spi.UriEndpoint;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriPath;
+import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.DefaultEndpoint;
+import org.apache.camel.support.processor.RestBindingAdvice;
+import org.apache.camel.util.UnsafeUriCharactersEncoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.camel.util.ObjectHelper.isNotEmpty;
+import static org.apache.camel.util.StringHelper.after;
+import static org.apache.camel.util.StringHelper.before;
+
+/**
+ * To call and expose REST services using a Postman Collection as contract.
+ */
+@UriEndpoint(firstVersion = "4.22.0", scheme = "rest-postman", title = "REST Postman",
+ syntax = "rest-postman:collectionSource#requestId", category = { Category.REST, Category.API },
+ headersClass = RestPostmanConstants.class)
+public class RestPostmanEndpoint extends DefaultEndpoint {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestPostmanEndpoint.class);
+
+ public static final String[] DEFAULT_REST_POSTMAN_CONSUMER_COMPONENTS = new String[] { "platform-http" };
+
+ /**
+ * Parameters of the endpoint URI that were not consumed as options, kept so that they can be used as literal path
+ * or query values.
+ */
+ Map parameters = Collections.emptyMap();
+
+ @UriPath(description = "The Postman Collection to use. Either a resource URI of a Collection v2.1 JSON document"
+ + " (classpath:, file: or http:), or the uid of a collection to fetch from the Postman"
+ + " cloud, which requires postmanApiKey.",
+ defaultValue = RestPostmanConfiguration.DEFAULT_COLLECTION_SOURCE,
+ defaultValueNote = "By default loads the postman-collection.json file")
+ private String collectionSource;
+
+ @UriPath(description = "The request to invoke, identified by its id in the collection or by its slugified name,"
+ + " for example getUserById. Use a folder id to run every request in that folder, and"
+ + " leave it out to run the whole collection. Append a slash to force a folder match when"
+ + " a request and a folder share a name.",
+ label = "producer")
+ private String requestId;
+
+ @UriParam
+ private RestPostmanConfiguration configuration = new RestPostmanConfiguration();
+
+ private RestPostmanProcessor postmanProcessor;
+
+ public RestPostmanEndpoint(String uri, String remaining, RestPostmanComponent component,
+ Map parameters) {
+ super(uri, component);
+
+ if (remaining != null && remaining.contains("#")) {
+ String fragment = after(remaining, "#");
+ // an empty fragment means the same as no fragment: the whole collection
+ requestId = fragment != null && !fragment.isEmpty() ? fragment : null;
+ String source = before(remaining, "#");
+ if (source != null && !source.isEmpty()) {
+ collectionSource = source;
+ }
+ } else if (remaining != null && !remaining.isEmpty()) {
+ if (looksLikeCollectionSource(remaining)) {
+ collectionSource = remaining;
+ } else {
+ requestId = remaining;
+ }
+ }
+
+ if (collectionSource == null) {
+ collectionSource = component.getCollectionSource();
+ }
+ if (collectionSource == null) {
+ collectionSource = RestPostmanConfiguration.DEFAULT_COLLECTION_SOURCE;
+ }
+
+ this.parameters = parameters;
+ setExchangePattern(ExchangePattern.InOut);
+ }
+
+ /**
+ * Distinguishes {@code rest-postman:my-api.json} from {@code rest-postman:getUserById} when no {@code #} was given.
+ */
+ private static boolean looksLikeCollectionSource(String remaining) {
+ return remaining.endsWith(".json")
+ || remaining.contains(":")
+ || PostmanCollectionLoader.isCloudSource(remaining, PostmanCollectionLoader.SOURCE_TYPE_AUTO);
+ }
+
+ @Override
+ public RestPostmanComponent getComponent() {
+ return (RestPostmanComponent) super.getComponent();
+ }
+
+ @Override
+ public boolean isLenientProperties() {
+ // unknown URI parameters are literal path or query values rather than mistakes
+ return true;
+ }
+
+ @Override
+ public Producer createProducer() throws Exception {
+ PostmanRequestIndex index = buildIndex();
+ PostmanRequestIndex.Selection selection = index.resolve(requestId);
+
+ List bindings = mapAll(selection.items());
+
+ if (selection.single()) {
+ PostmanRequestBinding binding = bindings.get(0);
+ Endpoint delegate = createDelegateEndpoint(binding);
+ return new RestPostmanProducer(delegate.createProducer(), binding.host() != null, binding);
+ }
+
+ // a folder or the whole collection: every request is run in turn, like Postman's collection runner
+ List prepared = new ArrayList<>(bindings.size());
+ for (PostmanRequestBinding binding : bindings) {
+ prepared.add(new RestPostmanRunnerProducer.PreparedRequest(
+ binding, createDelegateEndpoint(binding).createProducer()));
+ }
+ LOG.debug("Postman endpoint {} will run {} request(s) for {}", getEndpointUri(), prepared.size(),
+ selection.description());
+ return new RestPostmanRunnerProducer(this, prepared, configuration.isRunFailFast(), selection.description());
+ }
+
+ @Override
+ public Consumer createConsumer(Processor processor) throws Exception {
+ PostmanCollection collection = loadCollection();
+ PostmanRequestIndex index = PostmanRequestIndex.build(collection, configuration.getRequestFilter());
+ PostmanRequestIndex.Selection selection = index.resolve(requestId);
+ List bindings = mapAll(selection.items());
+
+ String path = determineConsumerBasePath(bindings);
+
+ RestPostmanProcessorStrategy strategy = getComponent().createProcessorStrategy();
+ // these are endpoint level options, so they have to come from this endpoint's configuration rather than
+ // from the component wide template the strategy was built with
+ if (configuration.getMissingRequest() != null) {
+ strategy.setMissingRequest(configuration.getMissingRequest());
+ }
+ if (configuration.getMockIncludePattern() != null) {
+ strategy.setMockIncludePattern(configuration.getMockIncludePattern());
+ }
+
+ RestPostmanProcessor restPostmanProcessor = new RestPostmanProcessor(
+ bindings, PostmanRedactor.redact(collection.getJson()), collectionSource, path,
+ configuration.getApiContextPath(), configuration.isClientRequestValidation(), strategy);
+ CamelContextAware.trySetCamelContext(restPostmanProcessor, getCamelContext());
+ this.postmanProcessor = restPostmanProcessor;
+
+ // the per-request binding advice replaces the stock one, exactly as the OpenAPI equivalent does
+ if (processor instanceof InternalProcessor ip) {
+ RestBindingAdvice advice = ip.getAdvice(RestBindingAdvice.class);
+ if (advice != null) {
+ ip.removeAdvice(advice);
+ }
+ ip.addAdvice(new RestPostmanProcessorAdvice(restPostmanProcessor));
+ }
+
+ Consumer consumer = createConsumerFor(path, restPostmanProcessor, processor);
+ restPostmanProcessor.setConsumer(consumer);
+ if (consumer instanceof PlatformHttpConsumerAware phca) {
+ phca.registerAfterConfigured(restPostmanProcessor);
+ }
+ return consumer;
+ }
+
+ /**
+ * A consumer serves one context path, but a collection can produce a different base path per request when its
+ * folders use different base URLs. The first one wins, and the rest are reported, because silently serving them on
+ * someone else's context path would be a confusing way to fail.
+ */
+ private String determineConsumerBasePath(List bindings) {
+ if (bindings.isEmpty()) {
+ return RestPostmanConfiguration.DEFAULT_BASE_PATH;
+ }
+ String path = bindings.get(0).basePath();
+ Set others = bindings.stream()
+ .map(PostmanRequestBinding::basePath)
+ .filter(other -> !path.equals(other))
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ if (!others.isEmpty()) {
+ LOG.warn("Postman collection {} yields more than one base path ({} and {}). Serving everything under"
+ + " {}; set the basePath option to choose explicitly.",
+ collectionSource, path, String.join(", ", others), path);
+ }
+ return path;
+ }
+
+ private Consumer createConsumerFor(String basePath, RestPostmanProcessor restPostmanProcessor, Processor processor)
+ throws Exception {
+ RestOpenApiConsumerFactory factory = null;
+ String cname = null;
+
+ if (configuration.getConsumerComponentName() != null) {
+ Object comp = getCamelContext().getRegistry().lookupByName(configuration.getConsumerComponentName());
+ if (comp instanceof RestOpenApiConsumerFactory rcf) {
+ factory = rcf;
+ } else {
+ comp = getCamelContext().getComponent(configuration.getConsumerComponentName());
+ if (comp instanceof RestOpenApiConsumerFactory rcf) {
+ factory = rcf;
+ }
+ }
+ if (factory == null) {
+ if (comp != null) {
+ throw new IllegalArgumentException(
+ "Component " + configuration.getConsumerComponentName()
+ + " is not a RestOpenApiConsumerFactory");
+ }
+ throw new NoSuchBeanException(
+ configuration.getConsumerComponentName(), RestOpenApiConsumerFactory.class.getName());
+ }
+ cname = configuration.getConsumerComponentName();
+ }
+
+ if (factory == null) {
+ for (String name : getCamelContext().getComponentNames()) {
+ Component comp = getCamelContext().getComponent(name);
+ if (comp instanceof RestOpenApiConsumerFactory rcf) {
+ factory = rcf;
+ cname = name;
+ break;
+ }
+ }
+ }
+
+ if (factory == null) {
+ for (String name : DEFAULT_REST_POSTMAN_CONSUMER_COMPONENTS) {
+ Object comp = getCamelContext().getComponent(name, true);
+ if (comp instanceof RestOpenApiConsumerFactory rcf) {
+ LOG.debug("Auto discovered {} as RestOpenApiConsumerFactory", name);
+ factory = rcf;
+ cname = name;
+ break;
+ }
+ }
+ }
+
+ if (factory == null) {
+ Set factories
+ = getCamelContext().getRegistry().findByType(RestOpenApiConsumerFactory.class);
+ if (factories != null && factories.size() == 1) {
+ factory = factories.iterator().next();
+ }
+ }
+
+ if (factory == null) {
+ throw new IllegalStateException(
+ "Cannot find RestOpenApiConsumerFactory in Registry or as a Component to use");
+ }
+
+ // fail closed: never start an unprotected consumer when oauthProfile is configured but the delegate
+ // factory does not declare that its consumers enforce it
+ if (isNotEmpty(configuration.getOauthProfile()) && !factory.supportsOAuthProfile()) {
+ throw new IllegalArgumentException(
+ "The oauthProfile option is not supported by the resolved RestOpenApiConsumerFactory ("
+ + factory.getClass().getName()
+ + "); select a consumer component that enforces oauthProfile");
+ }
+
+ RestConfiguration config = CamelContextHelper.getRestConfiguration(getCamelContext(), cname);
+ Map copy = new HashMap<>(parameters);
+ if (isNotEmpty(configuration.getOauthProfile())) {
+ copy.put("oauthProfile", configuration.getOauthProfile());
+ }
+ String contextPath = basePath;
+ if (contextPath.equals(config.getContextPath())) {
+ contextPath = "";
+ }
+
+ Consumer consumer = factory.createConsumer(getCamelContext(), processor, contextPath, config, copy);
+ if (consumer instanceof PlatformHttpConsumerAware phca) {
+ restPostmanProcessor.setPlatformHttpConsumer(phca);
+ }
+ configureConsumer(consumer);
+ return consumer;
+ }
+
+ /**
+ * Builds the {@code rest} endpoint that actually performs the call.
+ *
+ * Every option that distinguishes one request from another goes into the URI string, because endpoints are cached
+ * by URI and two requests differing only in, say, host must not share one (see CAMEL-24113).
+ */
+ private Endpoint createDelegateEndpoint(PostmanRequestBinding binding) {
+ Endpoint delegate = getCamelContext().getEndpoint(buildDelegateUri(binding));
+ delegate.configureProperties(determineEndpointParameters(binding));
+ return delegate;
+ }
+
+ /**
+ * Builds the URI of the delegate {@code rest} endpoint.
+ *
+ * Every option that distinguishes one request from another is part of the URI, because endpoints are cached by URI
+ * and two requests differing only in, say, host must not end up sharing one (CAMEL-24113).
+ */
+ String buildDelegateUri(PostmanRequestBinding binding) {
+ String uri = "rest:" + binding.method() + ":" + binding.basePath() + ":" + binding.uriTemplate();
+
+ StringBuilder query = new StringBuilder();
+ appendQuery(query, "host", binding.host());
+ appendQuery(query, "producerComponentName", configuration.getComponentName());
+ appendQuery(query, "consumes", binding.consumes());
+ appendQuery(query, "produces", binding.produces());
+ if (binding.queryParameters() != null) {
+ appendQuery(query, "queryParameters", UnsafeUriCharactersEncoder.encode(binding.queryParameters()));
+ }
+ if (!query.isEmpty()) {
+ uri = uri + "?" + query;
+ }
+ return uri;
+ }
+
+ private static void appendQuery(StringBuilder query, String name, String value) {
+ if (value == null) {
+ return;
+ }
+ if (!query.isEmpty()) {
+ query.append('&');
+ }
+ query.append(name).append('=').append(value);
+ }
+
+ private Map determineEndpointParameters(PostmanRequestBinding binding) {
+ Map answer = new LinkedHashMap<>();
+ if (binding.host() != null) {
+ answer.put("host", binding.host());
+ }
+ if (configuration.getComponentName() != null) {
+ answer.put("producerComponentName", configuration.getComponentName());
+ }
+ if (binding.consumes() != null) {
+ answer.put("consumes", binding.consumes());
+ }
+ if (binding.produces() != null) {
+ answer.put("produces", binding.produces());
+ }
+ if (binding.queryParameters() != null) {
+ answer.put("queryParameters", binding.queryParameters());
+ }
+
+ Map nested = new LinkedHashMap<>();
+ Map componentOptions = new LinkedHashMap<>();
+ componentOptions.put("useGlobalSslContextParameters", configuration.isUseGlobalSslContextParameters());
+ if (configuration.getSslContextParameters() != null) {
+ componentOptions.put("sslContextParameters", configuration.getSslContextParameters());
+ }
+ nested.put("component", componentOptions);
+
+ // leftover URI parameters are literal values, except where they name a path parameter, which would then be
+ // sent both in the path and as a query parameter
+ for (Map.Entry entry : parameters.entrySet()) {
+ if (!binding.defaultPathValues().containsKey(entry.getKey())
+ && !binding.uriTemplate().contains("{" + entry.getKey() + "}")) {
+ nested.put(entry.getKey(), entry.getValue());
+ }
+ }
+ answer.put("parameters", nested);
+ return answer;
+ }
+
+ private PostmanRequestIndex buildIndex() {
+ return PostmanRequestIndex.build(loadCollection(), configuration.getRequestFilter());
+ }
+
+ /**
+ * Resolves the URI fragment and maps every selected request, without creating any producer. Exposed so that the
+ * mapping can be asserted without an HTTP backend.
+ */
+ List resolveBindings() {
+ return mapAll(buildIndex().resolve(requestId).items());
+ }
+
+ private List mapAll(List items) {
+ PostmanRequestMapper mapper = new PostmanRequestMapper(
+ getCamelContext(), configuration, configuration.variablesAsStrings(), resourceOrigin());
+ List answer = new ArrayList<>(items.size());
+ for (PostmanItem item : items) {
+ answer.add(mapper.map(item));
+ }
+ return answer;
+ }
+
+ /**
+ * Loads the collection, through the component wide cache so that many endpoints over one collection cause one read,
+ * and for cloud sources one API call.
+ */
+ PostmanCollection loadCollection() {
+ boolean cloud = PostmanCollectionLoader.isCloudSource(collectionSource, configuration.getCollectionSourceType());
+ return getComponent().getCollectionCache().get(
+ collectionSource, configuration.getPostmanApiKey(), configuration.getCollectionCacheTtl(),
+ () -> cloud
+ ? PostmanCollectionLoader.loadFromCloud(createCloudClient(), collectionSource)
+ : PostmanCollectionLoader.loadFromResource(getCamelContext(), collectionSource));
+ }
+
+ private PostmanCloudClient createCloudClient() {
+ PostmanCloudClient.validateApiUrl(configuration.getPostmanApiUrl());
+ if (configuration.getPostmanApiKey() == null || configuration.getPostmanApiKey().isEmpty()) {
+ throw new IllegalArgumentException(
+ "postmanApiKey is required to fetch collection " + collectionSource + " from the Postman cloud."
+ + " Set collectionSourceType=resource if this is meant to be a local"
+ + " file rather than a collection uid.");
+ }
+ return new PostmanCloudClient(
+ configuration.getPostmanApiUrl(),
+ configuration.getPostmanApiKey(),
+ configuration.getPostmanApiKeyHeader(),
+ Duration.ofMillis(configuration.getConnectTimeout()),
+ Duration.ofMillis(configuration.getRequestTimeout()),
+ resolveSslContext());
+ }
+
+ private SSLContext resolveSslContext() {
+ try {
+ if (configuration.getSslContextParameters() != null) {
+ return configuration.getSslContextParameters().createSSLContext(getCamelContext());
+ }
+ if (configuration.isUseGlobalSslContextParameters()
+ && getCamelContext().getSSLContextParameters() != null) {
+ return getCamelContext().getSSLContextParameters().createSSLContext(getCamelContext());
+ }
+ } catch (Exception e) {
+ throw new RuntimeCamelException("Cannot create SSLContext for fetching the Postman collection", e);
+ }
+ return null;
+ }
+
+ /**
+ * The scheme and authority of the collection resource when it was loaded over HTTP, usable as a last resort for the
+ * target host.
+ *
+ * Deliberately {@code null} for cloud sources: their origin is {@code api.getpostman.com}, which is emphatically
+ * not the API the collection describes.
+ */
+ private String resourceOrigin() {
+ if (PostmanCollectionLoader.isCloudSource(collectionSource, configuration.getCollectionSourceType())) {
+ return null;
+ }
+ if (!collectionSource.startsWith("http://") && !collectionSource.startsWith("https://")) {
+ return null;
+ }
+ try {
+ URI uri = new URI(collectionSource);
+ StringBuilder answer = new StringBuilder(uri.getScheme()).append("://").append(uri.getHost());
+ if (uri.getPort() > 0) {
+ answer.append(':').append(uri.getPort());
+ }
+ return answer.toString();
+ } catch (Exception e) {
+ LOG.debug("Cannot derive an origin from collection source {}", collectionSource, e);
+ return null;
+ }
+ }
+
+ public String getCollectionSource() {
+ return collectionSource;
+ }
+
+ public void setCollectionSource(String collectionSource) {
+ this.collectionSource = collectionSource;
+ }
+
+ public String getRequestId() {
+ return requestId;
+ }
+
+ public void setRequestId(String requestId) {
+ this.requestId = requestId;
+ }
+
+ public RestPostmanConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public void setConfiguration(RestPostmanConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+ RestPostmanProcessor getPostmanProcessor() {
+ return postmanProcessor;
+ }
+}
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanHelper.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanHelper.java
new file mode 100644
index 0000000000000..d9fc1336bc54d
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanHelper.java
@@ -0,0 +1,183 @@
+/*
+ * 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.component.rest.postman;
+
+import java.text.Normalizer;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.util.StringHelper;
+
+/**
+ * Static helpers shared by the component, endpoint and consumer.
+ */
+public final class RestPostmanHelper {
+
+ private static final Pattern HOST_PATTERN = Pattern.compile("https?://[^:/]+(:\\d+)?", Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern UUID_PATTERN = Pattern.compile(
+ "(?:[0-9]+-)?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
+
+ /**
+ * An HTTP method, constrained so that a hostile or malformed collection cannot inject extra segments into the colon
+ * delimited {@code rest:} URI that is built from it.
+ */
+ private static final Pattern METHOD_PATTERN = Pattern.compile("[A-Za-z]{1,20}");
+
+ /**
+ * Characters that would break out of a path segment and corrupt the delegated endpoint URI.
+ */
+ private static final Pattern UNSAFE_SEGMENT = Pattern.compile("[?#&:]");
+
+ private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^a-zA-Z0-9]+");
+ private static final Pattern DIACRITICS = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+
+ private RestPostmanHelper() {
+ }
+
+ public static String isMediaRange(final String given, final String name) {
+ return StringHelper.notEmpty(given, name);
+ }
+
+ /**
+ * Validates that a host option is an absolute URI naming only a scheme, host and optional port.
+ */
+ public static String isHostParam(final String given) {
+ final String hostUri = StringHelper.notEmpty(given, "host");
+
+ final Matcher matcher = HOST_PATTERN.matcher(given);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(
+ "host must be an absolute URI (e.g. http://api.example.com), given: `" + hostUri + "`");
+ }
+ return hostUri;
+ }
+
+ /**
+ * Whether the text is a Postman UUID, or the {@code {ownerId}-{uuid}} uid form.
+ */
+ public static boolean isUuid(String text) {
+ return text != null && UUID_PATTERN.matcher(text).matches();
+ }
+
+ /**
+ * Validates an HTTP method read from a collection.
+ */
+ public static String validateMethod(String method, String itemDescription) {
+ if (method == null || !METHOD_PATTERN.matcher(method).matches()) {
+ throw new IllegalArgumentException(
+ "Postman request " + itemDescription + " declares an invalid HTTP method: " + method);
+ }
+ return method.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Validates a path segment after variable substitution.
+ *
+ * A Postman collection carries no schemas, so unlike the OpenAPI equivalent there are no Java types to bind to and
+ * {@code type}/{@code outType} are deliberately left unset. What remains is a best-effort notion of which headers,
+ * query parameters and body are required, inferred from what the collection actually declares.
+ */
+ private RestBindingConfiguration createRestBindingConfiguration(PostmanRequestBinding binding) {
+ RestConfiguration config = camelContext.getRestConfiguration();
+
+ RestBindingConfiguration bc = new RestBindingConfiguration();
+ bc.setBindingMode(config.getBindingMode().name());
+ bc.setEnableCORS(config.isEnableCORS());
+ bc.setCorsHeaders(config.getCorsHeaders());
+ bc.setClientRequestValidation(config.isClientRequestValidation() || clientRequestValidation);
+ bc.setEnableNoContentResponse(config.isEnableNoContentResponse());
+ bc.setSkipBindingOnErrorCode(config.isSkipBindingOnErrorCode());
+ bc.setConsumes(binding.produces());
+ bc.setProduces(producesOf(binding));
+ bc.setRequiredBody(binding.collectionBody() != null
+ && DefaultRestPostmanProcessorStrategy.expectsBody(binding.method()));
+ bc.setRequiredQueryParameters(requiredQueryParameters(binding));
+ bc.setRequiredHeaders(requiredHeaders(binding));
+ bc.setQueryDefaultValues(queryDefaultValues(binding));
+ return bc;
+ }
+
+ /**
+ * What the server would return, taken from the first saved example, since the collection describes no schemas.
+ */
+ private static String producesOf(PostmanRequestBinding binding) {
+ return binding.item().getSavedResponses().stream()
+ .map(PostmanResponse::getContentType)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ }
+
+ private static Set requiredQueryParameters(PostmanRequestBinding binding) {
+ // Postman has no "required" flag, so a parameter the author bothered to give a value to is the closest signal
+ Set answer = new LinkedHashSet<>();
+ for (PostmanKeyValue param : binding.item().getRequest().getUrl().getQueryParams()) {
+ if (param.hasValue()) {
+ answer.add(param.key());
+ }
+ }
+ return answer.isEmpty() ? null : answer;
+ }
+
+ private static Set requiredHeaders(PostmanRequestBinding binding) {
+ Set answer = new LinkedHashSet<>();
+ for (PostmanKeyValue header : binding.item().getRequest().getHeaders()) {
+ if (header.disabled()) {
+ continue;
+ }
+ String key = header.key();
+ // Content-Type and Accept are negotiated rather than required, and an auth header is supplied by the
+ // caller's own credentials rather than by the collection
+ if ("Content-Type".equalsIgnoreCase(key) || "Accept".equalsIgnoreCase(key)
+ || "Authorization".equalsIgnoreCase(key) || "Host".equalsIgnoreCase(key)) {
+ continue;
+ }
+ answer.add(key);
+ }
+ return answer.isEmpty() ? null : answer;
+ }
+
+ private static Map queryDefaultValues(PostmanRequestBinding binding) {
+ Map answer = new LinkedHashMap<>();
+ for (PostmanKeyValue param : binding.item().getRequest().getUrl().getQueryParams()) {
+ if (param.hasValue()) {
+ answer.put(param.key(), param.value());
+ }
+ }
+ return answer.isEmpty() ? null : answer;
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ super.doStop();
+ ServiceHelper.stopService(strategy);
+ for (var p : paths) {
+ if (p instanceof RestPostmanConsumerPath rcp) {
+ ServiceHelper.stopService(rcp.getBinding());
+ }
+ }
+ paths.clear();
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ public PlatformHttpConsumerAware getPlatformHttpConsumer() {
+ return platformHttpConsumer;
+ }
+
+ public void setPlatformHttpConsumer(PlatformHttpConsumerAware platformHttpConsumer) {
+ this.platformHttpConsumer = platformHttpConsumer;
+ }
+
+ public Consumer getConsumer() {
+ return consumer;
+ }
+
+ public void setConsumer(Consumer consumer) {
+ this.consumer = consumer;
+ }
+}
diff --git a/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanProcessorAdvice.java b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanProcessorAdvice.java
new file mode 100644
index 0000000000000..9bb8139789a6a
--- /dev/null
+++ b/components/camel-rest-postman/src/main/java/org/apache/camel/component/rest/postman/RestPostmanProcessorAdvice.java
@@ -0,0 +1,60 @@
+/*
+ * 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.component.rest.postman;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Ordered;
+import org.apache.camel.spi.CamelInternalProcessorAdvice;
+
+/**
+ * Runs the Postman request dispatcher after the rest of the internal processing, replacing the stock REST binding
+ * advice, which is not used because binding is decided per request rather than per route.
+ */
+class RestPostmanProcessorAdvice implements CamelInternalProcessorAdvice