From d7116344ca330d48fa613006c2679275c81b6766 Mon Sep 17 00:00:00 2001 From: Claudio Miranda Date: Mon, 20 Apr 2026 13:08:25 +0100 Subject: [PATCH] CAMEL-23351: camel-oaipmh: Add custom HTTP Headers --- .../camel/catalog/components/oaipmh.json | 36 ++--- .../component/OAIPMHEndpointConfigurer.java | 8 + .../component/OAIPMHEndpointUriFactory.java | 6 +- .../apache/camel/oaipmh/component/oaipmh.json | 36 ++--- .../oaipmh/component/OAIPMHConsumer.java | 3 + .../oaipmh/component/OAIPMHEndpoint.java | 37 +++++ .../oaipmh/component/OAIPMHProducer.java | 9 ++ .../component/model/OAIPMHConstants.java | 6 + .../camel/oaipmh/utils/OAIPMHHttpClient.java | 12 ++ .../oaipmh/OAIPMHConsumerHttpHeadersTest.java | 66 +++++++++ .../oaipmh/OAIPMHProducerHttpHeadersTest.java | 91 ++++++++++++ .../camel/oaipmh/utils/MockOaipmhServer.java | 4 + .../pages/camel-4x-upgrade-guide-4_20.adoc | 14 ++ .../dsl/OAIPMHEndpointBuilderFactory.java | 137 ++++++++++++++++++ 14 files changed, 429 insertions(+), 36 deletions(-) create mode 100644 components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/OAIPMHConsumerHttpHeadersTest.java create mode 100644 components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/OAIPMHProducerHttpHeadersTest.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/oaipmh.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/oaipmh.json index 43f49cf0c62eb..dc4a9a44587cd 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/oaipmh.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/oaipmh.json @@ -31,7 +31,8 @@ "healthCheckProducerEnabled": { "index": 4, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on producer checks globally by setting camel.health.producersEnabled=true." } }, "headers": { - "CamelOaimphResumptionToken": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "This header is obtained when onlyFirst option is enable. Return resumption token of the request when data is still available.", "constantName": "org.apache.camel.oaipmh.component.model.OAIPMHConstants#RESUMPTION_TOKEN" } + "CamelOaimphResumptionToken": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "This header is obtained when onlyFirst option is enable. Return resumption token of the request when data is still available.", "constantName": "org.apache.camel.oaipmh.component.model.OAIPMHConstants#RESUMPTION_TOKEN" }, + "CamelOaimphHttpHeaders": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Custom HTTP headers to send with the request, overriding any httpHeader. endpoint parameters.", "constantName": "org.apache.camel.oaipmh.component.model.OAIPMHConstants#HTTP_HEADERS" } }, "properties": { "baseUrl": { "index": 0, "kind": "path", "displayName": "Base Url", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Base URL of the repository to which the request is made through the OAI-PMH protocol" }, @@ -48,21 +49,22 @@ "pollStrategy": { "index": 11, "kind": "parameter", "displayName": "Poll Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your custom implementation to control error handling usually occurred during the poll operation before an Exchange have been created and being routed in Camel." }, "onlyFirst": { "index": 12, "kind": "parameter", "displayName": "Only First", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Returns the response of a single request. Otherwise it will make requests until there is no more data to return." }, "lazyStartProducer": { "index": 13, "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." }, - "backoffErrorThreshold": { "index": 14, "kind": "parameter", "displayName": "Backoff Error Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in." }, - "backoffIdleThreshold": { "index": 15, "kind": "parameter", "displayName": "Backoff Idle Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent idle polls that should happen before the backoffMultipler should kick-in." }, - "backoffMultiplier": { "index": 16, "kind": "parameter", "displayName": "Backoff Multiplier", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "To let the scheduled polling consumer backoff if there has been a number of subsequent idles\/errors in a row. The multiplier is then the number of polls that will be skipped before the next actual attempt is happening again. When this option is in use then backoffIdleThreshold and\/or backoffErrorThreshold must also be configured." }, - "delay": { "index": 17, "kind": "parameter", "displayName": "Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "description": "Milliseconds before the next poll." }, - "greedy": { "index": 18, "kind": "parameter", "displayName": "Greedy", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If greedy is enabled, then the ScheduledPollConsumer will run immediately again, if the previous run polled 1 or more messages." }, - "initialDelay": { "index": 19, "kind": "parameter", "displayName": "Initial Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "description": "Milliseconds before the first poll starts." }, - "repeatCount": { "index": 20, "kind": "parameter", "displayName": "Repeat Count", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "description": "Specifies a maximum limit of number of fires. So if you set it to 1, the scheduler will only fire once. If you set it to 5, it will only fire five times. A value of zero or negative means fire forever." }, - "runLoggingLevel": { "index": 21, "kind": "parameter", "displayName": "Run Logging Level", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "org.apache.camel.LoggingLevel", "enum": [ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "TRACE", "description": "The consumer logs a start\/complete log line when it polls. This option allows you to configure the logging level for that." }, - "scheduledExecutorService": { "index": 22, "kind": "parameter", "displayName": "Scheduled Executor Service", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.util.concurrent.ScheduledExecutorService", "deprecated": false, "autowired": false, "secret": false, "description": "Allows for configuring a custom\/shared thread pool to use for the consumer. By default each consumer has its own single threaded thread pool." }, - "scheduler": { "index": 23, "kind": "parameter", "displayName": "Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.lang.Object", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "description": "To use a cron scheduler from either camel-spring or camel-quartz component. Use value spring or quartz for built in scheduler" }, - "schedulerProperties": { "index": 24, "kind": "parameter", "displayName": "Scheduler Properties", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "scheduler.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional properties when using a custom scheduler or any of the Quartz, Spring based scheduler. This is a multi-value option with prefix: scheduler." }, - "startScheduler": { "index": 25, "kind": "parameter", "displayName": "Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether the scheduler should be auto started." }, - "timeUnit": { "index": 26, "kind": "parameter", "displayName": "Time Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", "DAYS" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and delay options." }, - "useFixedDelay": { "index": 27, "kind": "parameter", "displayName": "Use Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details." }, - "ignoreSSLWarnings": { "index": 28, "kind": "parameter", "displayName": "Ignore SSLWarnings", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Ignore SSL certificate warnings" }, - "ssl": { "index": 29, "kind": "parameter", "displayName": "Ssl", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Causes the defined url to make an https request" } + "httpHeaders": { "index": 14, "kind": "parameter", "displayName": "Http Headers", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "httpHeader.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "description": "Custom HTTP headers to send with each request to the OAI-PMH repository, for example for Authorization or Accept-Language. This is a multi-value option with prefix: httpHeader." }, + "backoffErrorThreshold": { "index": 15, "kind": "parameter", "displayName": "Backoff Error Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in." }, + "backoffIdleThreshold": { "index": 16, "kind": "parameter", "displayName": "Backoff Idle Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent idle polls that should happen before the backoffMultipler should kick-in." }, + "backoffMultiplier": { "index": 17, "kind": "parameter", "displayName": "Backoff Multiplier", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "To let the scheduled polling consumer backoff if there has been a number of subsequent idles\/errors in a row. The multiplier is then the number of polls that will be skipped before the next actual attempt is happening again. When this option is in use then backoffIdleThreshold and\/or backoffErrorThreshold must also be configured." }, + "delay": { "index": 18, "kind": "parameter", "displayName": "Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "description": "Milliseconds before the next poll." }, + "greedy": { "index": 19, "kind": "parameter", "displayName": "Greedy", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If greedy is enabled, then the ScheduledPollConsumer will run immediately again, if the previous run polled 1 or more messages." }, + "initialDelay": { "index": 20, "kind": "parameter", "displayName": "Initial Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "description": "Milliseconds before the first poll starts." }, + "repeatCount": { "index": 21, "kind": "parameter", "displayName": "Repeat Count", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "description": "Specifies a maximum limit of number of fires. So if you set it to 1, the scheduler will only fire once. If you set it to 5, it will only fire five times. A value of zero or negative means fire forever." }, + "runLoggingLevel": { "index": 22, "kind": "parameter", "displayName": "Run Logging Level", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "org.apache.camel.LoggingLevel", "enum": [ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "TRACE", "description": "The consumer logs a start\/complete log line when it polls. This option allows you to configure the logging level for that." }, + "scheduledExecutorService": { "index": 23, "kind": "parameter", "displayName": "Scheduled Executor Service", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.util.concurrent.ScheduledExecutorService", "deprecated": false, "autowired": false, "secret": false, "description": "Allows for configuring a custom\/shared thread pool to use for the consumer. By default each consumer has its own single threaded thread pool." }, + "scheduler": { "index": 24, "kind": "parameter", "displayName": "Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.lang.Object", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "description": "To use a cron scheduler from either camel-spring or camel-quartz component. Use value spring or quartz for built in scheduler" }, + "schedulerProperties": { "index": 25, "kind": "parameter", "displayName": "Scheduler Properties", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "scheduler.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional properties when using a custom scheduler or any of the Quartz, Spring based scheduler. This is a multi-value option with prefix: scheduler." }, + "startScheduler": { "index": 26, "kind": "parameter", "displayName": "Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether the scheduler should be auto started." }, + "timeUnit": { "index": 27, "kind": "parameter", "displayName": "Time Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", "DAYS" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and delay options." }, + "useFixedDelay": { "index": 28, "kind": "parameter", "displayName": "Use Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details." }, + "ignoreSSLWarnings": { "index": 29, "kind": "parameter", "displayName": "Ignore SSLWarnings", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Ignore SSL certificate warnings" }, + "ssl": { "index": 30, "kind": "parameter", "displayName": "Ssl", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Causes the defined url to make an https request" } } } diff --git a/components/camel-oaipmh/src/generated/java/org/apache/camel/oaipmh/component/OAIPMHEndpointConfigurer.java b/components/camel-oaipmh/src/generated/java/org/apache/camel/oaipmh/component/OAIPMHEndpointConfigurer.java index 960fafbbce808..6b219db587e52 100644 --- a/components/camel-oaipmh/src/generated/java/org/apache/camel/oaipmh/component/OAIPMHEndpointConfigurer.java +++ b/components/camel-oaipmh/src/generated/java/org/apache/camel/oaipmh/component/OAIPMHEndpointConfigurer.java @@ -38,6 +38,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "exchangePattern": target.setExchangePattern(property(camelContext, org.apache.camel.ExchangePattern.class, value)); return true; case "from": target.setFrom(property(camelContext, java.lang.String.class, value)); return true; case "greedy": target.setGreedy(property(camelContext, boolean.class, value)); return true; + case "httpheaders": + case "httpHeaders": target.setHttpHeaders(property(camelContext, java.util.Map.class, value)); return true; case "identifier": target.setIdentifier(property(camelContext, java.lang.String.class, value)); return true; case "ignoresslwarnings": case "ignoreSSLWarnings": target.setIgnoreSSLWarnings(property(camelContext, boolean.class, value)); return true; @@ -94,6 +96,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "exchangePattern": return org.apache.camel.ExchangePattern.class; case "from": return java.lang.String.class; case "greedy": return boolean.class; + case "httpheaders": + case "httpHeaders": return java.util.Map.class; case "identifier": return java.lang.String.class; case "ignoresslwarnings": case "ignoreSSLWarnings": return boolean.class; @@ -151,6 +155,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "exchangePattern": return target.getExchangePattern(); case "from": return target.getFrom(); case "greedy": return target.isGreedy(); + case "httpheaders": + case "httpHeaders": return target.getHttpHeaders(); case "identifier": return target.getIdentifier(); case "ignoresslwarnings": case "ignoreSSLWarnings": return target.isIgnoreSSLWarnings(); @@ -192,6 +198,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { @Override public Object getCollectionValueType(Object target, String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase() : name) { + case "httpheaders": + case "httpHeaders": return java.lang.String.class; case "schedulerproperties": case "schedulerProperties": return java.lang.Object.class; default: return null; diff --git a/components/camel-oaipmh/src/generated/java/org/apache/camel/oaipmh/component/OAIPMHEndpointUriFactory.java b/components/camel-oaipmh/src/generated/java/org/apache/camel/oaipmh/component/OAIPMHEndpointUriFactory.java index 9698630ce69c0..2b5bd8a3bb9d6 100644 --- a/components/camel-oaipmh/src/generated/java/org/apache/camel/oaipmh/component/OAIPMHEndpointUriFactory.java +++ b/components/camel-oaipmh/src/generated/java/org/apache/camel/oaipmh/component/OAIPMHEndpointUriFactory.java @@ -23,7 +23,7 @@ public class OAIPMHEndpointUriFactory extends org.apache.camel.support.component private static final Set SECRET_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(30); + Set props = new HashSet<>(31); props.add("backoffErrorThreshold"); props.add("backoffIdleThreshold"); props.add("backoffMultiplier"); @@ -34,6 +34,7 @@ public class OAIPMHEndpointUriFactory extends org.apache.camel.support.component props.add("exchangePattern"); props.add("from"); props.add("greedy"); + props.add("httpHeaders"); props.add("identifier"); props.add("ignoreSSLWarnings"); props.add("initialDelay"); @@ -56,7 +57,8 @@ public class OAIPMHEndpointUriFactory extends org.apache.camel.support.component props.add("verb"); PROPERTY_NAMES = Collections.unmodifiableSet(props); SECRET_PROPERTY_NAMES = Collections.emptySet(); - Map prefixes = new HashMap<>(1); + Map prefixes = new HashMap<>(2); + prefixes.put("httpHeaders", "httpHeader."); prefixes.put("schedulerProperties", "scheduler."); MULTI_VALUE_PREFIXES = Collections.unmodifiableMap(prefixes); } diff --git a/components/camel-oaipmh/src/generated/resources/META-INF/org/apache/camel/oaipmh/component/oaipmh.json b/components/camel-oaipmh/src/generated/resources/META-INF/org/apache/camel/oaipmh/component/oaipmh.json index 43f49cf0c62eb..dc4a9a44587cd 100644 --- a/components/camel-oaipmh/src/generated/resources/META-INF/org/apache/camel/oaipmh/component/oaipmh.json +++ b/components/camel-oaipmh/src/generated/resources/META-INF/org/apache/camel/oaipmh/component/oaipmh.json @@ -31,7 +31,8 @@ "healthCheckProducerEnabled": { "index": 4, "kind": "property", "displayName": "Health Check Producer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all producer based health checks from this component. Notice: Camel has by default disabled all producer based health-checks. You can turn on producer checks globally by setting camel.health.producersEnabled=true." } }, "headers": { - "CamelOaimphResumptionToken": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "This header is obtained when onlyFirst option is enable. Return resumption token of the request when data is still available.", "constantName": "org.apache.camel.oaipmh.component.model.OAIPMHConstants#RESUMPTION_TOKEN" } + "CamelOaimphResumptionToken": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "This header is obtained when onlyFirst option is enable. Return resumption token of the request when data is still available.", "constantName": "org.apache.camel.oaipmh.component.model.OAIPMHConstants#RESUMPTION_TOKEN" }, + "CamelOaimphHttpHeaders": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Custom HTTP headers to send with the request, overriding any httpHeader. endpoint parameters.", "constantName": "org.apache.camel.oaipmh.component.model.OAIPMHConstants#HTTP_HEADERS" } }, "properties": { "baseUrl": { "index": 0, "kind": "path", "displayName": "Base Url", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Base URL of the repository to which the request is made through the OAI-PMH protocol" }, @@ -48,21 +49,22 @@ "pollStrategy": { "index": 11, "kind": "parameter", "displayName": "Poll Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your custom implementation to control error handling usually occurred during the poll operation before an Exchange have been created and being routed in Camel." }, "onlyFirst": { "index": 12, "kind": "parameter", "displayName": "Only First", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Returns the response of a single request. Otherwise it will make requests until there is no more data to return." }, "lazyStartProducer": { "index": 13, "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." }, - "backoffErrorThreshold": { "index": 14, "kind": "parameter", "displayName": "Backoff Error Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in." }, - "backoffIdleThreshold": { "index": 15, "kind": "parameter", "displayName": "Backoff Idle Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent idle polls that should happen before the backoffMultipler should kick-in." }, - "backoffMultiplier": { "index": 16, "kind": "parameter", "displayName": "Backoff Multiplier", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "To let the scheduled polling consumer backoff if there has been a number of subsequent idles\/errors in a row. The multiplier is then the number of polls that will be skipped before the next actual attempt is happening again. When this option is in use then backoffIdleThreshold and\/or backoffErrorThreshold must also be configured." }, - "delay": { "index": 17, "kind": "parameter", "displayName": "Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "description": "Milliseconds before the next poll." }, - "greedy": { "index": 18, "kind": "parameter", "displayName": "Greedy", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If greedy is enabled, then the ScheduledPollConsumer will run immediately again, if the previous run polled 1 or more messages." }, - "initialDelay": { "index": 19, "kind": "parameter", "displayName": "Initial Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "description": "Milliseconds before the first poll starts." }, - "repeatCount": { "index": 20, "kind": "parameter", "displayName": "Repeat Count", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "description": "Specifies a maximum limit of number of fires. So if you set it to 1, the scheduler will only fire once. If you set it to 5, it will only fire five times. A value of zero or negative means fire forever." }, - "runLoggingLevel": { "index": 21, "kind": "parameter", "displayName": "Run Logging Level", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "org.apache.camel.LoggingLevel", "enum": [ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "TRACE", "description": "The consumer logs a start\/complete log line when it polls. This option allows you to configure the logging level for that." }, - "scheduledExecutorService": { "index": 22, "kind": "parameter", "displayName": "Scheduled Executor Service", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.util.concurrent.ScheduledExecutorService", "deprecated": false, "autowired": false, "secret": false, "description": "Allows for configuring a custom\/shared thread pool to use for the consumer. By default each consumer has its own single threaded thread pool." }, - "scheduler": { "index": 23, "kind": "parameter", "displayName": "Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.lang.Object", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "description": "To use a cron scheduler from either camel-spring or camel-quartz component. Use value spring or quartz for built in scheduler" }, - "schedulerProperties": { "index": 24, "kind": "parameter", "displayName": "Scheduler Properties", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "scheduler.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional properties when using a custom scheduler or any of the Quartz, Spring based scheduler. This is a multi-value option with prefix: scheduler." }, - "startScheduler": { "index": 25, "kind": "parameter", "displayName": "Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether the scheduler should be auto started." }, - "timeUnit": { "index": 26, "kind": "parameter", "displayName": "Time Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", "DAYS" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and delay options." }, - "useFixedDelay": { "index": 27, "kind": "parameter", "displayName": "Use Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details." }, - "ignoreSSLWarnings": { "index": 28, "kind": "parameter", "displayName": "Ignore SSLWarnings", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Ignore SSL certificate warnings" }, - "ssl": { "index": 29, "kind": "parameter", "displayName": "Ssl", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Causes the defined url to make an https request" } + "httpHeaders": { "index": 14, "kind": "parameter", "displayName": "Http Headers", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "httpHeader.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "description": "Custom HTTP headers to send with each request to the OAI-PMH repository, for example for Authorization or Accept-Language. This is a multi-value option with prefix: httpHeader." }, + "backoffErrorThreshold": { "index": 15, "kind": "parameter", "displayName": "Backoff Error Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in." }, + "backoffIdleThreshold": { "index": 16, "kind": "parameter", "displayName": "Backoff Idle Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent idle polls that should happen before the backoffMultipler should kick-in." }, + "backoffMultiplier": { "index": 17, "kind": "parameter", "displayName": "Backoff Multiplier", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "To let the scheduled polling consumer backoff if there has been a number of subsequent idles\/errors in a row. The multiplier is then the number of polls that will be skipped before the next actual attempt is happening again. When this option is in use then backoffIdleThreshold and\/or backoffErrorThreshold must also be configured." }, + "delay": { "index": 18, "kind": "parameter", "displayName": "Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "description": "Milliseconds before the next poll." }, + "greedy": { "index": 19, "kind": "parameter", "displayName": "Greedy", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If greedy is enabled, then the ScheduledPollConsumer will run immediately again, if the previous run polled 1 or more messages." }, + "initialDelay": { "index": 20, "kind": "parameter", "displayName": "Initial Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 1000, "description": "Milliseconds before the first poll starts." }, + "repeatCount": { "index": 21, "kind": "parameter", "displayName": "Repeat Count", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 0, "description": "Specifies a maximum limit of number of fires. So if you set it to 1, the scheduler will only fire once. If you set it to 5, it will only fire five times. A value of zero or negative means fire forever." }, + "runLoggingLevel": { "index": 22, "kind": "parameter", "displayName": "Run Logging Level", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "org.apache.camel.LoggingLevel", "enum": [ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "TRACE", "description": "The consumer logs a start\/complete log line when it polls. This option allows you to configure the logging level for that." }, + "scheduledExecutorService": { "index": 23, "kind": "parameter", "displayName": "Scheduled Executor Service", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.util.concurrent.ScheduledExecutorService", "deprecated": false, "autowired": false, "secret": false, "description": "Allows for configuring a custom\/shared thread pool to use for the consumer. By default each consumer has its own single threaded thread pool." }, + "scheduler": { "index": 24, "kind": "parameter", "displayName": "Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.lang.Object", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "description": "To use a cron scheduler from either camel-spring or camel-quartz component. Use value spring or quartz for built in scheduler" }, + "schedulerProperties": { "index": 25, "kind": "parameter", "displayName": "Scheduler Properties", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "scheduler.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional properties when using a custom scheduler or any of the Quartz, Spring based scheduler. This is a multi-value option with prefix: scheduler." }, + "startScheduler": { "index": 26, "kind": "parameter", "displayName": "Start Scheduler", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether the scheduler should be auto started." }, + "timeUnit": { "index": 27, "kind": "parameter", "displayName": "Time Unit", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "enum", "javaType": "java.util.concurrent.TimeUnit", "enum": [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS", "MINUTES", "HOURS", "DAYS" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "MILLISECONDS", "description": "Time unit for initialDelay and delay options." }, + "useFixedDelay": { "index": 28, "kind": "parameter", "displayName": "Use Fixed Delay", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Controls if fixed delay or fixed rate is used. See ScheduledExecutorService in JDK for details." }, + "ignoreSSLWarnings": { "index": 29, "kind": "parameter", "displayName": "Ignore SSLWarnings", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Ignore SSL certificate warnings" }, + "ssl": { "index": 30, "kind": "parameter", "displayName": "Ssl", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Causes the defined url to make an https request" } } } diff --git a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHConsumer.java b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHConsumer.java index b7da1771a566a..8c749a4684d87 100644 --- a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHConsumer.java +++ b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHConsumer.java @@ -51,6 +51,9 @@ protected void doStart() throws Exception { if (getEndpoint().isIgnoreSSLWarnings()) { this.harvester.getHttpClient().setIgnoreSSLWarnings(true); } + if (getEndpoint().getHttpHeaders() != null && !getEndpoint().getHttpHeaders().isEmpty()) { + this.harvester.getHttpClient().setHttpHeaders(getEndpoint().getHttpHeaders()); + } super.doStart(); } diff --git a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHEndpoint.java b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHEndpoint.java index 0b030a939952d..d1fbd3d74b9a2 100644 --- a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHEndpoint.java +++ b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHEndpoint.java @@ -17,6 +17,7 @@ package org.apache.camel.oaipmh.component; import java.net.URI; +import java.util.HashMap; import java.util.Map; import org.apache.camel.Category; @@ -75,6 +76,13 @@ public class OAIPMHEndpoint extends ScheduledPollEndpoint implements EndpointSer description = "Returns the response of a single request. Otherwise it will make requests until there is no more data to return.") private boolean onlyFirst; + @UriParam(label = "advanced", + description = "Custom HTTP headers to send with each request to the OAI-PMH repository, " + + "for example for Authorization or Accept-Language.", + javaType = "java.util.Map", + prefix = "httpHeader.", multiValue = true) + private Map httpHeaders; + private Map queryParameters; public OAIPMHEndpoint(String uri, String remaining, OAIPMHComponent component) { @@ -103,6 +111,27 @@ protected void doInit() throws Exception { validateParameters(); + // Collect httpHeader.* lenient params into the httpHeaders map so they become HTTP headers, + // not URL query parameters. This handles the URI-string form: ?httpHeader.Authorization=token + if (queryParameters != null) { + Map fromUri = new HashMap<>(); + queryParameters.entrySet().removeIf(entry -> { + if (entry.getKey().startsWith("httpHeader.")) { + fromUri.put(entry.getKey().substring("httpHeader.".length()), String.valueOf(entry.getValue())); + return true; + } + return false; + }); + if (!fromUri.isEmpty()) { + if (httpHeaders == null) { + httpHeaders = fromUri; + } else { + fromUri.putAll(httpHeaders); + httpHeaders = fromUri; + } + } + } + // build uri from parameters String prefix = ""; if (!baseUrl.startsWith("http:") && !baseUrl.startsWith("https:")) { @@ -223,4 +252,12 @@ public void setOnlyFirst(boolean onlyFist) { this.onlyFirst = onlyFist; } + public Map getHttpHeaders() { + return httpHeaders; + } + + public void setHttpHeaders(Map httpHeaders) { + this.httpHeaders = httpHeaders; + } + } diff --git a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHProducer.java b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHProducer.java index a7fdf84cd88fb..33d4b8ec0d01e 100644 --- a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHProducer.java +++ b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/OAIPMHProducer.java @@ -18,6 +18,7 @@ import java.net.URI; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import org.apache.camel.Exchange; @@ -50,6 +51,9 @@ public void process(Exchange exchange) throws Exception { endpoint.getFrom(), endpoint.getSet(), endpoint.getIdentifier()); + if (endpoint.getHttpHeaders() != null && !endpoint.getHttpHeaders().isEmpty()) { + harvester.getHttpClient().setHttpHeaders(endpoint.getHttpHeaders()); + } overrideHarvesterConfigs(exchange.getIn(), harvester); if (endpoint.isIgnoreSSLWarnings()) { harvester.getHttpClient().setIgnoreSSLWarnings(true); @@ -75,6 +79,11 @@ private void overrideHarvesterConfigs(Message msg, Harvester harvester) { checkAndSetConfigs(msg, OAIPMHConstants.RESUMPTION_TOKEN, harvester::setResumptionToken, String.class); checkAndSetConfigs(msg, OAIPMHConstants.ONLY_FIRST, endpoint::setOnlyFirst, Boolean.class); checkAndSetConfigs(msg, OAIPMHConstants.IGNORE_SSL_WARNINGS, endpoint::setIgnoreSSLWarnings, Boolean.class); + @SuppressWarnings("unchecked") + Map httpHeadersOverride = msg.getHeader(OAIPMHConstants.HTTP_HEADERS, Map.class); + if (httpHeadersOverride != null && !httpHeadersOverride.isEmpty()) { + harvester.getHttpClient().setHttpHeaders(httpHeadersOverride); + } } private void checkAndSetConfigs(final Message message, final String key, final Consumer fn, final Class type) { diff --git a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/model/OAIPMHConstants.java b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/model/OAIPMHConstants.java index 6aac8ff424ac4..92ca08b92e046 100644 --- a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/model/OAIPMHConstants.java +++ b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/component/model/OAIPMHConstants.java @@ -19,6 +19,7 @@ import org.apache.camel.spi.Metadata; public final class OAIPMHConstants { + @Metadata(label = "producer", description = "This header is obtained when onlyFirst option is enable. " + "Return resumption token of the request when data is still available.", javaType = "String") @@ -34,6 +35,11 @@ public final class OAIPMHConstants { public static final String SET = "CamelOaimphSet"; public static final String IDENTIFIER = "CamelOaimphIdentifier"; + @Metadata(label = "producer", + description = "Custom HTTP headers to send with the request, overriding any httpHeader.* endpoint parameters.", + javaType = "java.util.Map") + public static final String HTTP_HEADERS = "CamelOaimphHttpHeaders"; + private OAIPMHConstants() { } diff --git a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/utils/OAIPMHHttpClient.java b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/utils/OAIPMHHttpClient.java index 1854b511a1bf1..3339fc2837935 100644 --- a/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/utils/OAIPMHHttpClient.java +++ b/components/camel-oaipmh/src/main/java/org/apache/camel/oaipmh/utils/OAIPMHHttpClient.java @@ -24,6 +24,8 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.Map; import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.classic.methods.HttpGet; @@ -50,6 +52,7 @@ public class OAIPMHHttpClient { private static final Logger LOG = LoggerFactory.getLogger(OAIPMHHttpClient.class); private boolean ignoreSSLWarnings; + private Map httpHeaders = Collections.emptyMap(); public String doRequest( URI baseURI, String verb, String set, String from, String until, String metadataPrefix, String token, @@ -87,6 +90,7 @@ public String doRequest( } HttpGet httpget = new HttpGet(builder.build()); + httpHeaders.forEach(httpget::addHeader); LOG.info("Executing request: {} ", httpget); @@ -144,4 +148,12 @@ public void setIgnoreSSLWarnings(boolean ignoreSSLWarnings) { this.ignoreSSLWarnings = ignoreSSLWarnings; } + public Map getHttpHeaders() { + return httpHeaders; + } + + public void setHttpHeaders(Map httpHeaders) { + this.httpHeaders = httpHeaders != null ? httpHeaders : Collections.emptyMap(); + } + } diff --git a/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/OAIPMHConsumerHttpHeadersTest.java b/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/OAIPMHConsumerHttpHeadersTest.java new file mode 100644 index 0000000000000..1dd99393427e9 --- /dev/null +++ b/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/OAIPMHConsumerHttpHeadersTest.java @@ -0,0 +1,66 @@ +/* + * 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.oaipmh; + +import java.util.concurrent.TimeUnit; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.oaipmh.utils.MockOaipmhServer; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; +import static org.awaitility.Awaitility.await; + +public class OAIPMHConsumerHttpHeadersTest extends CamelTestSupport { + + private static MockOaipmhServer mockOaipmhServer; + + @BeforeAll + public static void startServer() { + mockOaipmhServer = MockOaipmhServer.create(); + mockOaipmhServer.start(); + } + + @AfterAll + public static void stopServer() { + mockOaipmhServer.stop(); + } + + @Test + public void testConsumerSendsCustomHeaders() { + await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> mockOaipmhServer.getServer().verify( + getRequestedFor(urlMatching("/oai/request.*")) + .withHeader("X-Custom-Header", equalTo("test-value")))); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + from("oaipmh://localhost:" + mockOaipmhServer.getHttpPort() + + "/oai/request?initialDelay=100&delay=60000" + + "&httpHeader.X-Custom-Header=test-value") + .to("mock:result"); + } + }; + } +} diff --git a/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/OAIPMHProducerHttpHeadersTest.java b/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/OAIPMHProducerHttpHeadersTest.java new file mode 100644 index 0000000000000..d0f34c5e99278 --- /dev/null +++ b/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/OAIPMHProducerHttpHeadersTest.java @@ -0,0 +1,91 @@ +/* + * 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.oaipmh; + +import java.util.Map; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.oaipmh.component.model.OAIPMHConstants; +import org.apache.camel.oaipmh.utils.MockOaipmhServer; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; + +public class OAIPMHProducerHttpHeadersTest extends CamelTestSupport { + + private static MockOaipmhServer mockOaipmhServer; + + @BeforeAll + public static void startServer() { + mockOaipmhServer = MockOaipmhServer.create(); + mockOaipmhServer.start(); + } + + @AfterAll + public static void stopServer() { + mockOaipmhServer.stop(); + } + + @BeforeEach + public void resetRequests() { + mockOaipmhServer.getServer().resetRequests(); + } + + @Test + public void testEndpointHttpHeadersSentToServer() throws Exception { + template.sendBody("direct:start", "foo"); + + mockOaipmhServer.getServer().verify( + getRequestedFor(urlMatching("/oai/request.*")) + .withHeader("Authorization", equalTo("test-token")) + .withHeader("X-Custom", equalTo("hello"))); + } + + @Test + public void testExchangeHeaderOverridesEndpointHeaders() throws Exception { + template.sendBodyAndHeader("direct:start-override", "foo", + OAIPMHConstants.HTTP_HEADERS, Map.of("Authorization", "runtime-token")); + + mockOaipmhServer.getServer().verify( + getRequestedFor(urlMatching("/oai/request.*")) + .withHeader("Authorization", equalTo("runtime-token"))); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + from("direct:start") + .to("oaipmh://localhost:" + mockOaipmhServer.getHttpPort() + + "/oai/request?onlyFirst=true" + + "&httpHeader.Authorization=test-token" + + "&httpHeader.X-Custom=hello"); + + from("direct:start-override") + .to("oaipmh://localhost:" + mockOaipmhServer.getHttpPort() + + "/oai/request?onlyFirst=true" + + "&httpHeader.Authorization=endpoint-token"); + } + }; + } +} diff --git a/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/utils/MockOaipmhServer.java b/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/utils/MockOaipmhServer.java index 6b063fa83fba8..3983dbfe1f05d 100644 --- a/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/utils/MockOaipmhServer.java +++ b/components/camel-oaipmh/src/test/java/org/apache/camel/oaipmh/utils/MockOaipmhServer.java @@ -129,6 +129,10 @@ public int getHttpsPort() { return this.httpsPort; } + public WireMockServer getServer() { + return server; + } + public static final class OaipmhMockTransformer extends ResponseDefinitionTransformer { @Override diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_20.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_20.adoc index d7f2f1429e544..87807bb22273c 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_20.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_20.adoc @@ -36,3 +36,17 @@ segments is no longer accepted and will result in an `InvalidTopicNameException` If your Camel routes use topic URIs with extra `/` characters in the topic name portion (e.g., `pulsar:persistent://public/default/my-topic/sub-path`), you must replace the extra `/` with another separator such as `-` (e.g., `pulsar:persistent://public/default/my-topic-sub-path`). + +=== camel-oaipmh + +A new parameter to allow users to set or override HTTP Headers for consumers or producers. + +For example to set the HTTP Header `Authorization`, use: `httpHeader.Authorization=test-token`. You can also set it from the Java DSL. + +```java +from("direct:start") + .to("oaipmh://my-server:8081" + + "/oai/request" + + "&httpHeader.Authorization=test-token" + + "&httpHeader.X-Custom=hello"); +``` diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OAIPMHEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OAIPMHEndpointBuilderFactory.java index eabfee2b222b8..2e32f71cca30f 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OAIPMHEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/OAIPMHEndpointBuilderFactory.java @@ -847,6 +847,47 @@ default AdvancedOAIPMHEndpointConsumerBuilder pollStrategy(String pollStrategy) doSetProperty("pollStrategy", pollStrategy); return this; } + /** + * Custom HTTP headers to send with each request to the OAI-PMH + * repository, for example for Authorization or Accept-Language. This is + * a multi-value option with prefix: httpHeader. + * + * The option is a: java.util.Map<java.lang.String, + * java.lang.String> type. + * The option is multivalued, and you can use the httpHeaders(String, + * Object) method to add a value (call the method multiple times to set + * more values). + * + * Group: advanced + * + * @param key the option key + * @param value the option value + * @return the dsl builder + */ + default AdvancedOAIPMHEndpointConsumerBuilder httpHeaders(String key, Object value) { + doSetMultiValueProperty("httpHeaders", "httpHeader." + key, value); + return this; + } + /** + * Custom HTTP headers to send with each request to the OAI-PMH + * repository, for example for Authorization or Accept-Language. This is + * a multi-value option with prefix: httpHeader. + * + * The option is a: java.util.Map<java.lang.String, + * java.lang.String> type. + * The option is multivalued, and you can use the httpHeaders(String, + * Object) method to add a value (call the method multiple times to set + * more values). + * + * Group: advanced + * + * @param values the values + * @return the dsl builder + */ + default AdvancedOAIPMHEndpointConsumerBuilder httpHeaders(Map values) { + doSetMultiValueProperties("httpHeaders", "httpHeader.", values); + return this; + } } /** @@ -1098,6 +1139,47 @@ default AdvancedOAIPMHEndpointProducerBuilder lazyStartProducer(String lazyStart doSetProperty("lazyStartProducer", lazyStartProducer); return this; } + /** + * Custom HTTP headers to send with each request to the OAI-PMH + * repository, for example for Authorization or Accept-Language. This is + * a multi-value option with prefix: httpHeader. + * + * The option is a: java.util.Map<java.lang.String, + * java.lang.String> type. + * The option is multivalued, and you can use the httpHeaders(String, + * Object) method to add a value (call the method multiple times to set + * more values). + * + * Group: advanced + * + * @param key the option key + * @param value the option value + * @return the dsl builder + */ + default AdvancedOAIPMHEndpointProducerBuilder httpHeaders(String key, Object value) { + doSetMultiValueProperty("httpHeaders", "httpHeader." + key, value); + return this; + } + /** + * Custom HTTP headers to send with each request to the OAI-PMH + * repository, for example for Authorization or Accept-Language. This is + * a multi-value option with prefix: httpHeader. + * + * The option is a: java.util.Map<java.lang.String, + * java.lang.String> type. + * The option is multivalued, and you can use the httpHeaders(String, + * Object) method to add a value (call the method multiple times to set + * more values). + * + * Group: advanced + * + * @param values the values + * @return the dsl builder + */ + default AdvancedOAIPMHEndpointProducerBuilder httpHeaders(Map values) { + doSetMultiValueProperties("httpHeaders", "httpHeader.", values); + return this; + } } /** @@ -1275,6 +1357,47 @@ default OAIPMHEndpointBuilder basic() { return (OAIPMHEndpointBuilder) this; } + /** + * Custom HTTP headers to send with each request to the OAI-PMH + * repository, for example for Authorization or Accept-Language. This is + * a multi-value option with prefix: httpHeader. + * + * The option is a: java.util.Map<java.lang.String, + * java.lang.String> type. + * The option is multivalued, and you can use the httpHeaders(String, + * Object) method to add a value (call the method multiple times to set + * more values). + * + * Group: advanced + * + * @param key the option key + * @param value the option value + * @return the dsl builder + */ + default AdvancedOAIPMHEndpointBuilder httpHeaders(String key, Object value) { + doSetMultiValueProperty("httpHeaders", "httpHeader." + key, value); + return this; + } + /** + * Custom HTTP headers to send with each request to the OAI-PMH + * repository, for example for Authorization or Accept-Language. This is + * a multi-value option with prefix: httpHeader. + * + * The option is a: java.util.Map<java.lang.String, + * java.lang.String> type. + * The option is multivalued, and you can use the httpHeaders(String, + * Object) method to add a value (call the method multiple times to set + * more values). + * + * Group: advanced + * + * @param values the values + * @return the dsl builder + */ + default AdvancedOAIPMHEndpointBuilder httpHeaders(Map values) { + doSetMultiValueProperties("httpHeaders", "httpHeader.", values); + return this; + } } public interface OAIPMHBuilders { @@ -1358,6 +1481,20 @@ public static class OAIPMHHeaderNameBuilder { public String oaimphResumptionToken() { return "CamelOaimphResumptionToken"; } + /** + * Custom HTTP headers to send with the request, overriding any + * httpHeader. endpoint parameters. + * + * The option is a: {@code java.util.Map} type. + * + * Group: producer + * + * @return the name of the header {@code OaimphHttpHeaders}. + */ + public String oaimphHttpHeaders() { + return "CamelOaimphHttpHeaders"; + } } static OAIPMHEndpointBuilder endpointBuilder(String componentName, String path) { class OAIPMHEndpointBuilderImpl extends AbstractEndpointBuilder implements OAIPMHEndpointBuilder, AdvancedOAIPMHEndpointBuilder {