From 5cec3c611978025789184a236b76a18e2ef6facb Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Fri, 17 Jul 2026 12:34:12 +0200 Subject: [PATCH 01/13] Add camel-apicurio-registry component New Camel component wrapping the Apicurio Registry v3 REST API via the apicurio-registry-java-sdk. Assisted by Claude, model: claude-opus-4-6 --- catalog/camel-allcomponents/pom.xml | 5 + components/camel-apicurio-registry/pom.xml | 86 ++++++ .../ApicurioRegistryComponentConfigurer.java | 145 ++++++++++ .../ApicurioRegistryEndpointConfigurer.java | 243 +++++++++++++++++ .../ApicurioRegistryEndpointUriFactory.java | 121 +++++++++ ...RegistryProducerInvokeOnHeaderFactory.java | 46 ++++ .../apicurioregistry/apicurio-registry.json | 102 ++++++++ .../org/apache/camel/component.properties | 7 + .../apache/camel/component/apicurio-registry | 2 + .../configurer/apicurio-registry-component | 2 + .../configurer/apicurio-registry-endpoint | 2 + ....apicurioregistry.ApicurioRegistryProducer | 2 + .../urifactory/apicurio-registry-endpoint | 2 + .../docs/apicurio-registry-component.adoc | 198 ++++++++++++++ .../ApicurioRegistryComponent.java | 66 +++++ .../ApicurioRegistryConfiguration.java | 201 ++++++++++++++ .../ApicurioRegistryConstants.java | 79 ++++++ .../ApicurioRegistryConsumer.java | 99 +++++++ .../ApicurioRegistryEndpoint.java | 132 ++++++++++ .../ApicurioRegistryProducer.java | 247 ++++++++++++++++++ .../ApicurioRegistryValidationException.java | 33 +++ .../ApicurioRegistryComponentTest.java | 89 +++++++ .../ApicurioRegistryConsumerTest.java | 112 ++++++++ .../ApicurioRegistryProducerTest.java | 209 +++++++++++++++ .../ApicurioRegistryConsumerIT.java | 95 +++++++ .../ApicurioRegistryProducerIT.java | 212 +++++++++++++++ .../ApicurioRegistryTestSupport.java | 41 +++ .../ApicurioRegistryValidateIT.java | 132 ++++++++++ components/pom.xml | 1 + parent/pom.xml | 5 + .../pom.xml | 41 +++ .../common/ApicurioRegistryProperties.java | 28 ++ .../ApicurioRegistryInfraService.java | 28 ++ ...rioRegistryLocalContainerInfraService.java | 104 ++++++++ .../ApicurioRegistryRemoteInfraService.java | 58 ++++ .../services/ApicurioRegistryService.java | 23 ++ .../ApicurioRegistryServiceFactory.java | 46 ++++ .../registry/services/container.properties | 17 ++ test-infra/pom.xml | 1 + 39 files changed, 3062 insertions(+) create mode 100644 components/camel-apicurio-registry/pom.xml create mode 100644 components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java create mode 100644 components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java create mode 100644 components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java create mode 100644 components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerInvokeOnHeaderFactory.java create mode 100644 components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json create mode 100644 components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties create mode 100644 components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component/apicurio-registry create mode 100644 components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/configurer/apicurio-registry-component create mode 100644 components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/configurer/apicurio-registry-endpoint create mode 100644 components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.component.apicurioregistry.ApicurioRegistryProducer create mode 100644 components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/urifactory/apicurio-registry-endpoint create mode 100644 components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc create mode 100644 components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java create mode 100644 components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java create mode 100644 components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java create mode 100644 components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java create mode 100644 components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java create mode 100644 components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java create mode 100644 components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryValidationException.java create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryTestSupport.java create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java create mode 100644 test-infra/camel-test-infra-apicurio-registry/pom.xml create mode 100644 test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/common/ApicurioRegistryProperties.java create mode 100644 test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryInfraService.java create mode 100644 test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java create mode 100644 test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryRemoteInfraService.java create mode 100644 test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryService.java create mode 100644 test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryServiceFactory.java create mode 100644 test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties diff --git a/catalog/camel-allcomponents/pom.xml b/catalog/camel-allcomponents/pom.xml index a87cb4a7b19a7..16542806b7f01 100644 --- a/catalog/camel-allcomponents/pom.xml +++ b/catalog/camel-allcomponents/pom.xml @@ -137,6 +137,11 @@ camel-amqp ${project.version} + + org.apache.camel + camel-apicurio-registry + ${project.version} + org.apache.camel camel-arangodb diff --git a/components/camel-apicurio-registry/pom.xml b/components/camel-apicurio-registry/pom.xml new file mode 100644 index 0000000000000..6745134f6042c --- /dev/null +++ b/components/camel-apicurio-registry/pom.xml @@ -0,0 +1,86 @@ + + + + + 4.0.0 + + + components + org.apache.camel + 4.22.0-SNAPSHOT + + + camel-apicurio-registry + jar + + Camel :: Apicurio Registry + Camel Apicurio Registry component + + + 3.3.0 + + + + + + org.apache.camel + camel-support + + + + + io.apicurio + apicurio-registry-java-sdk + ${apicurio-registry-sdk-version} + + + + + org.apache.camel + camel-test-junit5 + test + + + org.apache.camel + camel-direct + test + + + org.apache.camel + camel-mock + test + + + org.mockito + mockito-core + ${mockito-version} + test + + + + + org.apache.camel + camel-test-infra-apicurio-registry + ${project.version} + test + + + + diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java new file mode 100644 index 0000000000000..4f83f5274502f --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java @@ -0,0 +1,145 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.apicurioregistry; + +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 ApicurioRegistryComponentConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + private org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration getOrCreateConfiguration(ApicurioRegistryComponent target) { + if (target.getConfiguration() == null) { + target.setConfiguration(new org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration()); + } + return target.getConfiguration(); + } + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + ApicurioRegistryComponent target = (ApicurioRegistryComponent) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "artifacttype": + case "artifactType": getOrCreateConfiguration(target).setArtifactType(property(camelContext, java.lang.String.class, value)); return true; + case "authtype": + case "authType": getOrCreateConfiguration(target).setAuthType(property(camelContext, java.lang.String.class, value)); return true; + case "autowiredenabled": + case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; + case "bridgeerrorhandler": + case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; + case "cachettl": + case "cacheTtl": getOrCreateConfiguration(target).setCacheTtl(property(camelContext, long.class, value)); return true; + case "clientid": + case "clientId": getOrCreateConfiguration(target).setClientId(property(camelContext, java.lang.String.class, value)); return true; + case "clientsecret": + case "clientSecret": getOrCreateConfiguration(target).setClientSecret(property(camelContext, java.lang.String.class, value)); return true; + case "configuration": target.setConfiguration(property(camelContext, org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration.class, value)); return true; + case "failonvalidation": + case "failOnValidation": getOrCreateConfiguration(target).setFailOnValidation(property(camelContext, boolean.class, value)); return true; + case "fetchcontent": + case "fetchContent": getOrCreateConfiguration(target).setFetchContent(property(camelContext, boolean.class, value)); return true; + case "ifexists": + case "ifExists": getOrCreateConfiguration(target).setIfExists(property(camelContext, java.lang.String.class, value)); return true; + case "lazystartproducer": + case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + case "operation": getOrCreateConfiguration(target).setOperation(property(camelContext, java.lang.String.class, value)); return true; + case "password": getOrCreateConfiguration(target).setPassword(property(camelContext, java.lang.String.class, value)); return true; + case "registryurl": + case "registryUrl": getOrCreateConfiguration(target).setRegistryUrl(property(camelContext, java.lang.String.class, value)); return true; + case "scope": getOrCreateConfiguration(target).setScope(property(camelContext, java.lang.String.class, value)); return true; + case "tokenendpoint": + case "tokenEndpoint": getOrCreateConfiguration(target).setTokenEndpoint(property(camelContext, java.lang.String.class, value)); return true; + case "username": getOrCreateConfiguration(target).setUsername(property(camelContext, java.lang.String.class, value)); return true; + default: return false; + } + } + + @Override + public Class getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "artifacttype": + case "artifactType": return java.lang.String.class; + case "authtype": + case "authType": return java.lang.String.class; + case "autowiredenabled": + case "autowiredEnabled": return boolean.class; + case "bridgeerrorhandler": + case "bridgeErrorHandler": return boolean.class; + case "cachettl": + case "cacheTtl": return long.class; + case "clientid": + case "clientId": return java.lang.String.class; + case "clientsecret": + case "clientSecret": return java.lang.String.class; + case "configuration": return org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration.class; + case "failonvalidation": + case "failOnValidation": return boolean.class; + case "fetchcontent": + case "fetchContent": return boolean.class; + case "ifexists": + case "ifExists": return java.lang.String.class; + case "lazystartproducer": + case "lazyStartProducer": return boolean.class; + case "operation": return java.lang.String.class; + case "password": return java.lang.String.class; + case "registryurl": + case "registryUrl": return java.lang.String.class; + case "scope": return java.lang.String.class; + case "tokenendpoint": + case "tokenEndpoint": return java.lang.String.class; + case "username": return java.lang.String.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + ApicurioRegistryComponent target = (ApicurioRegistryComponent) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "artifacttype": + case "artifactType": return getOrCreateConfiguration(target).getArtifactType(); + case "authtype": + case "authType": return getOrCreateConfiguration(target).getAuthType(); + case "autowiredenabled": + case "autowiredEnabled": return target.isAutowiredEnabled(); + case "bridgeerrorhandler": + case "bridgeErrorHandler": return target.isBridgeErrorHandler(); + case "cachettl": + case "cacheTtl": return getOrCreateConfiguration(target).getCacheTtl(); + case "clientid": + case "clientId": return getOrCreateConfiguration(target).getClientId(); + case "clientsecret": + case "clientSecret": return getOrCreateConfiguration(target).getClientSecret(); + case "configuration": return target.getConfiguration(); + case "failonvalidation": + case "failOnValidation": return getOrCreateConfiguration(target).isFailOnValidation(); + case "fetchcontent": + case "fetchContent": return getOrCreateConfiguration(target).isFetchContent(); + case "ifexists": + case "ifExists": return getOrCreateConfiguration(target).getIfExists(); + case "lazystartproducer": + case "lazyStartProducer": return target.isLazyStartProducer(); + case "operation": return getOrCreateConfiguration(target).getOperation(); + case "password": return getOrCreateConfiguration(target).getPassword(); + case "registryurl": + case "registryUrl": return getOrCreateConfiguration(target).getRegistryUrl(); + case "scope": return getOrCreateConfiguration(target).getScope(); + case "tokenendpoint": + case "tokenEndpoint": return getOrCreateConfiguration(target).getTokenEndpoint(); + case "username": return getOrCreateConfiguration(target).getUsername(); + default: return null; + } + } +} + diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java new file mode 100644 index 0000000000000..e4916994a870b --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java @@ -0,0 +1,243 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.apicurioregistry; + +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 ApicurioRegistryEndpointConfigurer extends PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + ApicurioRegistryEndpoint target = (ApicurioRegistryEndpoint) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "artifacttype": + case "artifactType": target.getConfiguration().setArtifactType(property(camelContext, java.lang.String.class, value)); return true; + case "authtype": + case "authType": target.getConfiguration().setAuthType(property(camelContext, java.lang.String.class, value)); return true; + case "backofferrorthreshold": + case "backoffErrorThreshold": target.setBackoffErrorThreshold(property(camelContext, int.class, value)); return true; + case "backoffidlethreshold": + case "backoffIdleThreshold": target.setBackoffIdleThreshold(property(camelContext, int.class, value)); return true; + case "backoffmultiplier": + case "backoffMultiplier": target.setBackoffMultiplier(property(camelContext, int.class, value)); return true; + case "bridgeerrorhandler": + case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; + case "cachettl": + case "cacheTtl": target.getConfiguration().setCacheTtl(property(camelContext, long.class, value)); return true; + case "clientid": + case "clientId": target.getConfiguration().setClientId(property(camelContext, java.lang.String.class, value)); return true; + case "clientsecret": + case "clientSecret": target.getConfiguration().setClientSecret(property(camelContext, java.lang.String.class, value)); return true; + case "delay": target.setDelay(property(camelContext, long.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 "failonvalidation": + case "failOnValidation": target.getConfiguration().setFailOnValidation(property(camelContext, boolean.class, value)); return true; + case "fetchcontent": + case "fetchContent": target.getConfiguration().setFetchContent(property(camelContext, boolean.class, value)); return true; + case "greedy": target.setGreedy(property(camelContext, boolean.class, value)); return true; + case "ifexists": + case "ifExists": target.getConfiguration().setIfExists(property(camelContext, java.lang.String.class, value)); return true; + case "initialdelay": + case "initialDelay": target.setInitialDelay(property(camelContext, long.class, value)); return true; + case "lazystartproducer": + case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + case "operation": target.getConfiguration().setOperation(property(camelContext, java.lang.String.class, value)); return true; + case "password": target.getConfiguration().setPassword(property(camelContext, java.lang.String.class, value)); return true; + case "pollstrategy": + case "pollStrategy": target.setPollStrategy(property(camelContext, org.apache.camel.spi.PollingConsumerPollStrategy.class, value)); return true; + case "registryclient": + case "registryClient": target.setRegistryClient(property(camelContext, io.apicurio.registry.rest.client.RegistryClient.class, value)); return true; + case "registryurl": + case "registryUrl": target.getConfiguration().setRegistryUrl(property(camelContext, java.lang.String.class, value)); return true; + case "repeatcount": + case "repeatCount": target.setRepeatCount(property(camelContext, long.class, value)); return true; + case "runlogginglevel": + case "runLoggingLevel": target.setRunLoggingLevel(property(camelContext, org.apache.camel.LoggingLevel.class, value)); return true; + case "scheduledexecutorservice": + case "scheduledExecutorService": target.setScheduledExecutorService(property(camelContext, java.util.concurrent.ScheduledExecutorService.class, value)); return true; + case "scheduler": target.setScheduler(property(camelContext, java.lang.Object.class, value)); return true; + case "schedulerproperties": + case "schedulerProperties": target.setSchedulerProperties(property(camelContext, java.util.Map.class, value)); return true; + case "scope": target.getConfiguration().setScope(property(camelContext, java.lang.String.class, value)); return true; + case "sendemptymessagewhenidle": + case "sendEmptyMessageWhenIdle": target.setSendEmptyMessageWhenIdle(property(camelContext, boolean.class, value)); return true; + case "startscheduler": + case "startScheduler": target.setStartScheduler(property(camelContext, boolean.class, value)); return true; + case "timeunit": + case "timeUnit": target.setTimeUnit(property(camelContext, java.util.concurrent.TimeUnit.class, value)); return true; + case "tokenendpoint": + case "tokenEndpoint": target.getConfiguration().setTokenEndpoint(property(camelContext, java.lang.String.class, value)); return true; + case "usefixeddelay": + case "useFixedDelay": target.setUseFixedDelay(property(camelContext, boolean.class, value)); return true; + case "username": target.getConfiguration().setUsername(property(camelContext, java.lang.String.class, value)); return true; + default: return false; + } + } + + @Override + public Class getOptionType(String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "artifacttype": + case "artifactType": return java.lang.String.class; + case "authtype": + case "authType": return java.lang.String.class; + case "backofferrorthreshold": + case "backoffErrorThreshold": return int.class; + case "backoffidlethreshold": + case "backoffIdleThreshold": return int.class; + case "backoffmultiplier": + case "backoffMultiplier": return int.class; + case "bridgeerrorhandler": + case "bridgeErrorHandler": return boolean.class; + case "cachettl": + case "cacheTtl": return long.class; + case "clientid": + case "clientId": return java.lang.String.class; + case "clientsecret": + case "clientSecret": return java.lang.String.class; + case "delay": return long.class; + case "exceptionhandler": + case "exceptionHandler": return org.apache.camel.spi.ExceptionHandler.class; + case "exchangepattern": + case "exchangePattern": return org.apache.camel.ExchangePattern.class; + case "failonvalidation": + case "failOnValidation": return boolean.class; + case "fetchcontent": + case "fetchContent": return boolean.class; + case "greedy": return boolean.class; + case "ifexists": + case "ifExists": return java.lang.String.class; + case "initialdelay": + case "initialDelay": return long.class; + case "lazystartproducer": + case "lazyStartProducer": return boolean.class; + case "operation": return java.lang.String.class; + case "password": return java.lang.String.class; + case "pollstrategy": + case "pollStrategy": return org.apache.camel.spi.PollingConsumerPollStrategy.class; + case "registryclient": + case "registryClient": return io.apicurio.registry.rest.client.RegistryClient.class; + case "registryurl": + case "registryUrl": return java.lang.String.class; + case "repeatcount": + case "repeatCount": return long.class; + case "runlogginglevel": + case "runLoggingLevel": return org.apache.camel.LoggingLevel.class; + case "scheduledexecutorservice": + case "scheduledExecutorService": return java.util.concurrent.ScheduledExecutorService.class; + case "scheduler": return java.lang.Object.class; + case "schedulerproperties": + case "schedulerProperties": return java.util.Map.class; + case "scope": return java.lang.String.class; + case "sendemptymessagewhenidle": + case "sendEmptyMessageWhenIdle": return boolean.class; + case "startscheduler": + case "startScheduler": return boolean.class; + case "timeunit": + case "timeUnit": return java.util.concurrent.TimeUnit.class; + case "tokenendpoint": + case "tokenEndpoint": return java.lang.String.class; + case "usefixeddelay": + case "useFixedDelay": return boolean.class; + case "username": return java.lang.String.class; + default: return null; + } + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + ApicurioRegistryEndpoint target = (ApicurioRegistryEndpoint) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "artifacttype": + case "artifactType": return target.getConfiguration().getArtifactType(); + case "authtype": + case "authType": return target.getConfiguration().getAuthType(); + case "backofferrorthreshold": + case "backoffErrorThreshold": return target.getBackoffErrorThreshold(); + case "backoffidlethreshold": + case "backoffIdleThreshold": return target.getBackoffIdleThreshold(); + case "backoffmultiplier": + case "backoffMultiplier": return target.getBackoffMultiplier(); + case "bridgeerrorhandler": + case "bridgeErrorHandler": return target.isBridgeErrorHandler(); + case "cachettl": + case "cacheTtl": return target.getConfiguration().getCacheTtl(); + case "clientid": + case "clientId": return target.getConfiguration().getClientId(); + case "clientsecret": + case "clientSecret": return target.getConfiguration().getClientSecret(); + case "delay": return target.getDelay(); + case "exceptionhandler": + case "exceptionHandler": return target.getExceptionHandler(); + case "exchangepattern": + case "exchangePattern": return target.getExchangePattern(); + case "failonvalidation": + case "failOnValidation": return target.getConfiguration().isFailOnValidation(); + case "fetchcontent": + case "fetchContent": return target.getConfiguration().isFetchContent(); + case "greedy": return target.isGreedy(); + case "ifexists": + case "ifExists": return target.getConfiguration().getIfExists(); + case "initialdelay": + case "initialDelay": return target.getInitialDelay(); + case "lazystartproducer": + case "lazyStartProducer": return target.isLazyStartProducer(); + case "operation": return target.getConfiguration().getOperation(); + case "password": return target.getConfiguration().getPassword(); + case "pollstrategy": + case "pollStrategy": return target.getPollStrategy(); + case "registryclient": + case "registryClient": return target.getRegistryClient(); + case "registryurl": + case "registryUrl": return target.getConfiguration().getRegistryUrl(); + case "repeatcount": + case "repeatCount": return target.getRepeatCount(); + case "runlogginglevel": + case "runLoggingLevel": return target.getRunLoggingLevel(); + case "scheduledexecutorservice": + case "scheduledExecutorService": return target.getScheduledExecutorService(); + case "scheduler": return target.getScheduler(); + case "schedulerproperties": + case "schedulerProperties": return target.getSchedulerProperties(); + case "scope": return target.getConfiguration().getScope(); + case "sendemptymessagewhenidle": + case "sendEmptyMessageWhenIdle": return target.isSendEmptyMessageWhenIdle(); + case "startscheduler": + case "startScheduler": return target.isStartScheduler(); + case "timeunit": + case "timeUnit": return target.getTimeUnit(); + case "tokenendpoint": + case "tokenEndpoint": return target.getConfiguration().getTokenEndpoint(); + case "usefixeddelay": + case "useFixedDelay": return target.isUseFixedDelay(); + case "username": return target.getConfiguration().getUsername(); + default: return null; + } + } + + @Override + public Object getCollectionValueType(Object target, String name, boolean ignoreCase) { + switch (ignoreCase ? name.toLowerCase() : name) { + case "schedulerproperties": + case "schedulerProperties": return java.lang.Object.class; + default: return null; + } + } +} + diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java new file mode 100644 index 0000000000000..90577aab7f31e --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java @@ -0,0 +1,121 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.apicurioregistry; + +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 ApicurioRegistryEndpointUriFactory extends org.apache.camel.support.component.EndpointUriFactorySupport implements EndpointUriFactory { + + private static final String BASE = ":groupId/artifactId"; + + 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<>(37); + props.add("artifactId"); + props.add("artifactType"); + props.add("authType"); + props.add("backoffErrorThreshold"); + props.add("backoffIdleThreshold"); + props.add("backoffMultiplier"); + props.add("bridgeErrorHandler"); + props.add("cacheTtl"); + props.add("clientId"); + props.add("clientSecret"); + props.add("delay"); + props.add("exceptionHandler"); + props.add("exchangePattern"); + props.add("failOnValidation"); + props.add("fetchContent"); + props.add("greedy"); + props.add("groupId"); + props.add("ifExists"); + props.add("initialDelay"); + props.add("lazyStartProducer"); + props.add("operation"); + props.add("password"); + props.add("pollStrategy"); + props.add("registryClient"); + props.add("registryUrl"); + props.add("repeatCount"); + props.add("runLoggingLevel"); + props.add("scheduledExecutorService"); + props.add("scheduler"); + props.add("schedulerProperties"); + props.add("scope"); + props.add("sendEmptyMessageWhenIdle"); + props.add("startScheduler"); + props.add("timeUnit"); + props.add("tokenEndpoint"); + props.add("useFixedDelay"); + props.add("username"); + PROPERTY_NAMES = Collections.unmodifiableSet(props); + Set secretProps = new HashSet<>(4); + secretProps.add("clientId"); + secretProps.add("clientSecret"); + secretProps.add("password"); + secretProps.add("username"); + SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps); + ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet(); + Map prefixes = new HashMap<>(1); + prefixes.put("schedulerProperties", "scheduler."); + MULTI_VALUE_PREFIXES = Collections.unmodifiableMap(prefixes); + } + + @Override + public boolean isEnabled(String scheme) { + return "apicurio-registry".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, "groupId", null, false, copy); + uri = buildPathParameter(syntax, uri, "artifactId", 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-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerInvokeOnHeaderFactory.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerInvokeOnHeaderFactory.java new file mode 100644 index 0000000000000..4135109707e99 --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerInvokeOnHeaderFactory.java @@ -0,0 +1,46 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.component.apicurioregistry; + +import javax.annotation.processing.Generated; + +import org.apache.camel.AsyncCallback; +import org.apache.camel.Exchange; +import org.apache.camel.spi.InvokeOnHeaderStrategy; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryProducer; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@SuppressWarnings("unchecked") +@Generated("org.apache.camel.maven.packaging.GenerateInvokeOnHeaderMojo") +public class ApicurioRegistryProducerInvokeOnHeaderFactory implements InvokeOnHeaderStrategy { + + @Override + public Object invoke(Object obj, String key, Exchange exchange, AsyncCallback callback) throws Exception { + org.apache.camel.component.apicurioregistry.ApicurioRegistryProducer target = (org.apache.camel.component.apicurioregistry.ApicurioRegistryProducer) obj; + switch (key) { + case "createartifact": + case "createArtifact": target.createArtifact(exchange.getMessage()); return null; + case "creategroup": + case "createGroup": target.createGroup(exchange.getMessage()); return null; + case "deleteartifact": + case "deleteArtifact": target.deleteArtifact(exchange.getMessage()); return null; + case "getartifactcontent": + case "getArtifactContent": target.getArtifactContent(exchange.getMessage()); return null; + case "getartifactmetadata": + case "getArtifactMetadata": target.getArtifactMetadata(exchange.getMessage()); return null; + case "listversions": + case "listVersions": target.listVersions(exchange.getMessage()); return null; + case "searchartifacts": + case "searchArtifacts": target.searchArtifacts(exchange.getMessage()); return null; + case "testcompatibility": + case "testCompatibility": target.testCompatibility(exchange.getMessage()); return null; + case "updateartifact": + case "updateArtifact": target.updateArtifact(exchange.getMessage()); return null; + case "validate": target.validate(exchange.getMessage()); return null; + default: return null; + } + } + +} + diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json new file mode 100644 index 0000000000000..82bea993998e5 --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json @@ -0,0 +1,102 @@ +{ + "component": { + "kind": "component", + "name": "apicurio-registry", + "title": "Apicurio Registry", + "description": "Manage artifacts, versions, and groups in Apicurio Registry v3.", + "deprecated": false, + "firstVersion": "4.22.0", + "label": "cloud,api", + "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent", + "supportLevel": "Preview", + "groupId": "org.apache.camel", + "artifactId": "camel-apicurio-registry", + "version": "4.22.0-SNAPSHOT", + "scheme": "apicurio-registry", + "extendsScheme": "", + "syntax": "apicurio-registry:groupId\/artifactId", + "async": false, + "api": false, + "consumerOnly": false, + "producerOnly": false, + "lenientProperties": false, + "browsable": false, + "remote": true + }, + "componentProperties": { + "registryUrl": { "index": 0, "kind": "property", "displayName": "Registry Url", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The Apicurio Registry base URL" }, + "bridgeErrorHandler": { "index": 1, "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." }, + "fetchContent": { "index": 2, "kind": "property", "displayName": "Fetch Content", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to fetch the content for each new version found by the consumer" }, + "artifactType": { "index": 3, "kind": "property", "displayName": "Artifact Type", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "JSON", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The default artifact type" }, + "cacheTtl": { "index": 4, "kind": "property", "displayName": "Cache Ttl", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 300000, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Schema cache TTL in milliseconds for the validate operation. 0 means no caching." }, + "failOnValidation": { "index": 5, "kind": "property", "displayName": "Fail On Validation", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead." }, + "ifExists": { "index": 6, "kind": "property", "displayName": "If Exists", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "FAIL", "CREATE_VERSION", "FIND_OR_CREATE_VERSION" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "FAIL", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Behavior when artifact already exists" }, + "lazyStartProducer": { "index": 7, "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." }, + "operation": { "index": 8, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, + "autowiredEnabled": { "index": 9, "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." }, + "configuration": { "index": 10, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, + "authType": { "index": 11, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 12, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 13, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 14, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 15, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "tokenEndpoint": { "index": 16, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 17, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + }, + "headers": { + "CamelApicurioRegistryOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_OPERATION" }, + "CamelApicurioRegistryGroupId": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact group ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_GROUP_ID" }, + "CamelApicurioRegistryArtifactId": { "index": 2, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_ID" }, + "CamelApicurioRegistryArtifactType": { "index": 3, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact type (e.g. AVRO, PROTOBUF, JSON, OPENAPI)", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_TYPE" }, + "CamelApicurioRegistryVersion": { "index": 4, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact version expression", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION" }, + "CamelApicurioRegistryArtifactName": { "index": 5, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact name", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_NAME" }, + "CamelApicurioRegistryArtifactDescription": { "index": 6, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact description", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_DESCRIPTION" }, + "CamelApicurioRegistryIfExists": { "index": 7, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Behavior when artifact already exists (FAIL, CREATE_VERSION, FIND_OR_CREATE_VERSION)", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_IF_EXISTS" }, + "CamelApicurioRegistryContentType": { "index": 8, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type of the artifact", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_TYPE" }, + "CamelApicurioRegistryDryRun": { "index": 9, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the operation is a dry run", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_DRY_RUN" }, + "CamelApicurioRegistryGlobalId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version global ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_GLOBAL_ID" }, + "CamelApicurioRegistryContentId": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, + "CamelApicurioRegistryVersionState": { "index": 12, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, + "CamelApicurioRegistryValidationResult": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether validation passed", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, + "CamelApicurioRegistryValidationErrors": { "index": 14, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Validation error details", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } + }, + "properties": { + "groupId": { "index": 0, "kind": "path", "displayName": "Group Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact group ID" }, + "artifactId": { "index": 1, "kind": "path", "displayName": "Artifact Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact ID" }, + "registryUrl": { "index": 2, "kind": "parameter", "displayName": "Registry Url", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The Apicurio Registry base URL" }, + "fetchContent": { "index": 3, "kind": "parameter", "displayName": "Fetch Content", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to fetch the content for each new version found by the consumer" }, + "sendEmptyMessageWhenIdle": { "index": 4, "kind": "parameter", "displayName": "Send Empty Message When Idle", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If the polling consumer did not poll any files, you can enable this option to send an empty message (no body) instead." }, + "bridgeErrorHandler": { "index": 5, "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." }, + "exceptionHandler": { "index": 6, "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": 7, "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." }, + "pollStrategy": { "index": 8, "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." }, + "artifactType": { "index": 9, "kind": "parameter", "displayName": "Artifact Type", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "JSON", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The default artifact type" }, + "cacheTtl": { "index": 10, "kind": "parameter", "displayName": "Cache Ttl", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 300000, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Schema cache TTL in milliseconds for the validate operation. 0 means no caching." }, + "failOnValidation": { "index": 11, "kind": "parameter", "displayName": "Fail On Validation", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead." }, + "ifExists": { "index": 12, "kind": "parameter", "displayName": "If Exists", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "FAIL", "CREATE_VERSION", "FIND_OR_CREATE_VERSION" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "FAIL", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Behavior when artifact already exists" }, + "operation": { "index": 13, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, + "lazyStartProducer": { "index": 14, "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." }, + "registryClient": { "index": 15, "kind": "parameter", "displayName": "Registry Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.apicurio.registry.rest.client.RegistryClient", "deprecated": false, "autowired": false, "secret": false, "description": "To use a pre-configured RegistryClient instance" }, + "backoffErrorThreshold": { "index": 16, "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": 17, "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": 18, "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": 19, "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": 20, "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": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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": 26, "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": 27, "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": 28, "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": 29, "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." }, + "authType": { "index": 30, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 31, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 32, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 33, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 34, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "tokenEndpoint": { "index": 35, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 36, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + } +} diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties new file mode 100644 index 0000000000000..d66643d9caeb5 --- /dev/null +++ b/components/camel-apicurio-registry/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=apicurio-registry +groupId=org.apache.camel +artifactId=camel-apicurio-registry +version=4.22.0-SNAPSHOT +projectName=Camel :: Apicurio Registry +projectDescription=Camel Apicurio Registry component diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component/apicurio-registry b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component/apicurio-registry new file mode 100644 index 0000000000000..50039a4ef48d3 --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component/apicurio-registry @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/configurer/apicurio-registry-component b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/configurer/apicurio-registry-component new file mode 100644 index 0000000000000..a094536fd4c8a --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/configurer/apicurio-registry-component @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.apicurioregistry.ApicurioRegistryComponentConfigurer diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/configurer/apicurio-registry-endpoint b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/configurer/apicurio-registry-endpoint new file mode 100644 index 0000000000000..b6875b4afe891 --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/configurer/apicurio-registry-endpoint @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.apicurioregistry.ApicurioRegistryEndpointConfigurer diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.component.apicurioregistry.ApicurioRegistryProducer b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.component.apicurioregistry.ApicurioRegistryProducer new file mode 100644 index 0000000000000..7ae68a23a7bc4 --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/invoke-on-header/org.apache.camel.component.apicurioregistry.ApicurioRegistryProducer @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.apicurioregistry.ApicurioRegistryProducerInvokeOnHeaderFactory diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/urifactory/apicurio-registry-endpoint b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/urifactory/apicurio-registry-endpoint new file mode 100644 index 0000000000000..a92bb3a88709f --- /dev/null +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/urifactory/apicurio-registry-endpoint @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.component.apicurioregistry.ApicurioRegistryEndpointUriFactory diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc new file mode 100644 index 0000000000000..950f4c6e8c3c9 --- /dev/null +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -0,0 +1,198 @@ += Apicurio Registry Component +:doctitle: Apicurio Registry +:shortname: apicurio-registry +:artifactid: camel-apicurio-registry +:description: Manage artifacts, versions, and groups in Apicurio Registry v3. +:since: 4.22 +:supportlevel: Preview +:tabs-sync-option: +:component-header: Both producer and consumer are supported + +*Since Camel {since}* + +*{component-header}* + +The Apicurio Registry component provides integration with https://www.apicur.io/registry/[Apicurio Registry] v3 +using the https://github.com/Apicurio/apicurio-registry[Apicurio Registry Java SDK]. + +It supports CRUD operations on artifacts, groups, and versions, as well as schema validation +and polling for new artifact versions. + +Maven users will need to add the following dependency to their `pom.xml` +for this component: + +[source,xml] +---- + + org.apache.camel + camel-apicurio-registry + x.x.x + + +---- + +== URI format + +---- +apicurio-registry:groupId/artifactId[?options] +---- + +Where `groupId` and `artifactId` identify the registry artifact. Both are optional for +operations that do not require them (e.g., `searchArtifacts`, `createGroup`). + +// component options: START +include::partial$component-configure-options.adoc[] +include::partial$component-endpoint-options.adoc[] +include::partial$component-endpoint-headers.adoc[] +// component options: END + +== Producer Operations + +The producer supports the following operations, set via the `operation` endpoint option or +the `CamelApicurioRegistryOperation` header: + +[cols="1,3"] +|=== +| Operation | Description + +| `createArtifact` | Create a new artifact. Body contains the artifact content. +| `updateArtifact` | Create a new version of an existing artifact. Body contains the new content. +| `deleteArtifact` | Delete an artifact. +| `getArtifactContent` | Retrieve the content of an artifact version. +| `getArtifactMetadata` | Retrieve artifact metadata. +| `searchArtifacts` | Search for artifacts using filter headers. +| `listVersions` | List all versions of an artifact. +| `createGroup` | Create a new group. +| `testCompatibility` | Test schema compatibility (dry run). Returns `true`/`false` in the body. +| `validate` | Validate the message body against the artifact's rules (dry run). Throws `ApicurioRegistryValidationException` on failure when `failOnValidation=true`. +|=== + +== Consumer + +The consumer polls the registry for new versions of the specified artifact. It tracks the last +seen `globalId` and only delivers new versions. Set the `delay` option to control the polling +interval (default: 500ms). + +When `fetchContent=true`, the consumer fetches the version content and sets it as the exchange body. +Otherwise, the version metadata object is set as the body. + +== Examples + +=== Create an artifact + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:create") + .setBody(constant("{\"type\":\"record\",\"name\":\"Test\",\"fields\":[]}")) + .to("apicurio-registry:myGroup/myArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=createArtifact&artifactType=AVRO"); +---- + +XML:: ++ +[source,xml] +---- + + + {"type":"record","name":"Test","fields":[]} + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:create + steps: + - setBody: + constant: '{"type":"record","name":"Test","fields":[]}' + - to: + uri: apicurio-registry:myGroup/myArtifact + parameters: + registryUrl: http://localhost:8080/apis/registry/v3 + operation: createArtifact + artifactType: AVRO +---- +==== + +=== Poll for new versions + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("apicurio-registry:myGroup/myArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=5000") + .log("New version detected: ${header.CamelApicurioRegistryVersion}") + .to("log:newVersions"); +---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: apicurio-registry:myGroup/myArtifact + parameters: + registryUrl: http://localhost:8080/apis/registry/v3 + delay: 5000 + steps: + - log: + message: "New version detected: ${header.CamelApicurioRegistryVersion}" + - to: + uri: log:newVersions +---- +==== + +=== Validate message against a schema + +[source,java] +---- +from("platform-http:/orders") + .doTry() + .to("apicurio-registry:my-group/order-schema?operation=validate®istryUrl=http://localhost:8080/apis/registry/v3") + .to("bean:orderService") + .doCatch(ApicurioRegistryValidationException.class) + .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400)) + .setBody(simple("${exception.validationErrors}")) + .end(); +---- + +== Authentication + +The component supports three authentication modes via the `authType` option: + +* `none` (default): No authentication. +* `basic`: Basic authentication using `username` and `password`. +* `oidc`: OAuth2/OIDC using `tokenEndpoint`, `clientId`, `clientSecret`, and optionally `scope`. + +Example with OIDC: + +[source,java] +---- +from("direct:secured") + .to("apicurio-registry:myGroup/myArtifact?registryUrl=http://localhost:8080/apis/registry/v3" + + "&operation=getArtifactContent" + + "&authType=oidc" + + "&tokenEndpoint=http://keycloak:8080/realms/registry/protocol/openid-connect/token" + + "&clientId=registry-client&clientSecret=secret"); +---- diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java new file mode 100644 index 0000000000000..7d84b31de215e --- /dev/null +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.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.component.apicurioregistry; + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.annotations.Component; +import org.apache.camel.support.DefaultComponent; + +@Component("apicurio-registry") +public class ApicurioRegistryComponent extends DefaultComponent { + + @Metadata(label = "advanced", description = "The component configuration") + private ApicurioRegistryConfiguration configuration = new ApicurioRegistryConfiguration(); + + public ApicurioRegistryComponent() { + } + + public ApicurioRegistryComponent(CamelContext context) { + super(context); + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception { + ApicurioRegistryConfiguration config = this.configuration.copy(); + + String groupId = null; + String artifactId = null; + if (remaining != null && !remaining.isEmpty()) { + String[] parts = remaining.split("/", 2); + groupId = parts[0]; + if (parts.length > 1 && !parts[1].isEmpty()) { + artifactId = parts[1]; + } + } + + ApicurioRegistryEndpoint endpoint = new ApicurioRegistryEndpoint(uri, this, config, groupId, artifactId); + setProperties(endpoint, parameters); + return endpoint; + } + + public ApicurioRegistryConfiguration getConfiguration() { + return configuration; + } + + public void setConfiguration(ApicurioRegistryConfiguration configuration) { + this.configuration = configuration; + } +} diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java new file mode 100644 index 0000000000000..99f3091e81a80 --- /dev/null +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java @@ -0,0 +1,201 @@ +/* + * 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.apicurioregistry; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.Metadata; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriParams; + +@UriParams +public class ApicurioRegistryConfiguration implements Cloneable { + + @UriParam(label = "common", description = "The Apicurio Registry base URL") + @Metadata(required = true) + private String registryUrl; + + @UriParam(label = "producer", + enums = "createArtifact,updateArtifact,deleteArtifact,getArtifactContent,getArtifactMetadata,searchArtifacts,listVersions,createGroup,testCompatibility,validate", + description = "The operation to perform") + private String operation; + + @UriParam(label = "producer", description = "The default artifact type", defaultValue = "JSON") + private String artifactType = "JSON"; + + @UriParam(label = "security", enums = "none,basic,oidc", defaultValue = "none", + description = "The authentication type to use") + private String authType = "none"; + + @UriParam(label = "security", security = "secret", description = "Username for basic authentication") + private String username; + + @UriParam(label = "security", security = "secret", description = "Password for basic authentication") + private String password; + + @UriParam(label = "security", description = "OAuth2 token endpoint URL") + private String tokenEndpoint; + + @UriParam(label = "security", security = "secret", description = "OAuth2 client ID") + private String clientId; + + @UriParam(label = "security", security = "secret", description = "OAuth2 client secret") + private String clientSecret; + + @UriParam(label = "security", description = "OAuth2 scope") + private String scope; + + @UriParam(label = "producer", + description = "Behavior when artifact already exists", + enums = "FAIL,CREATE_VERSION,FIND_OR_CREATE_VERSION", + defaultValue = "FAIL") + private String ifExists = "FAIL"; + + @UriParam(label = "consumer", + description = "Whether to fetch the content for each new version found by the consumer", + defaultValue = "false") + private boolean fetchContent; + + @UriParam(label = "producer", + description = "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead.", + defaultValue = "true") + private boolean failOnValidation = true; + + @UriParam(label = "producer", + description = "Schema cache TTL in milliseconds for the validate operation. 0 means no caching.", + defaultValue = "300000") + private long cacheTtl = 300000; + + public String getRegistryUrl() { + return registryUrl; + } + + public void setRegistryUrl(String registryUrl) { + this.registryUrl = registryUrl; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public String getArtifactType() { + return artifactType; + } + + public void setArtifactType(String artifactType) { + this.artifactType = artifactType; + } + + public String getAuthType() { + return authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getTokenEndpoint() { + return tokenEndpoint; + } + + public void setTokenEndpoint(String tokenEndpoint) { + this.tokenEndpoint = tokenEndpoint; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getIfExists() { + return ifExists; + } + + public void setIfExists(String ifExists) { + this.ifExists = ifExists; + } + + public boolean isFetchContent() { + return fetchContent; + } + + public void setFetchContent(boolean fetchContent) { + this.fetchContent = fetchContent; + } + + public boolean isFailOnValidation() { + return failOnValidation; + } + + public void setFailOnValidation(boolean failOnValidation) { + this.failOnValidation = failOnValidation; + } + + public long getCacheTtl() { + return cacheTtl; + } + + public void setCacheTtl(long cacheTtl) { + this.cacheTtl = cacheTtl; + } + + public ApicurioRegistryConfiguration copy() { + try { + return (ApicurioRegistryConfiguration) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeCamelException(e); + } + } +} diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java new file mode 100644 index 0000000000000..f190390be696f --- /dev/null +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java @@ -0,0 +1,79 @@ +/* + * 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.apicurioregistry; + +import org.apache.camel.spi.Metadata; + +public interface ApicurioRegistryConstants { + + @Metadata(description = "The operation to perform", javaType = "String") + String HEADER_OPERATION = "CamelApicurioRegistryOperation"; + + @Metadata(description = "The artifact group ID", javaType = "String") + String HEADER_GROUP_ID = "CamelApicurioRegistryGroupId"; + + @Metadata(description = "The artifact ID", javaType = "String") + String HEADER_ARTIFACT_ID = "CamelApicurioRegistryArtifactId"; + + @Metadata(description = "The artifact type (e.g. AVRO, PROTOBUF, JSON, OPENAPI)", javaType = "String") + String HEADER_ARTIFACT_TYPE = "CamelApicurioRegistryArtifactType"; + + @Metadata(description = "The artifact version expression", javaType = "String") + String HEADER_VERSION = "CamelApicurioRegistryVersion"; + + @Metadata(description = "The artifact name", javaType = "String") + String HEADER_ARTIFACT_NAME = "CamelApicurioRegistryArtifactName"; + + @Metadata(description = "The artifact description", javaType = "String") + String HEADER_ARTIFACT_DESCRIPTION = "CamelApicurioRegistryArtifactDescription"; + + @Metadata(description = "Behavior when artifact already exists (FAIL, CREATE_VERSION, FIND_OR_CREATE_VERSION)", + javaType = "String") + String HEADER_IF_EXISTS = "CamelApicurioRegistryIfExists"; + + @Metadata(description = "The content type of the artifact", javaType = "String") + String HEADER_CONTENT_TYPE = "CamelApicurioRegistryContentType"; + + @Metadata(description = "Whether the operation is a dry run", javaType = "Boolean") + String HEADER_DRY_RUN = "CamelApicurioRegistryDryRun"; + + @Metadata(label = "consumer", description = "The version global ID", javaType = "Long") + String HEADER_GLOBAL_ID = "CamelApicurioRegistryGlobalId"; + + @Metadata(label = "consumer", description = "The content ID", javaType = "Long") + String HEADER_CONTENT_ID = "CamelApicurioRegistryContentId"; + + @Metadata(label = "consumer", description = "The version state", javaType = "String") + String HEADER_VERSION_STATE = "CamelApicurioRegistryVersionState"; + + @Metadata(description = "Whether validation passed", javaType = "Boolean") + String HEADER_VALIDATION_RESULT = "CamelApicurioRegistryValidationResult"; + + @Metadata(description = "Validation error details", javaType = "String") + String HEADER_VALIDATION_ERRORS = "CamelApicurioRegistryValidationErrors"; + + String OPERATION_CREATE_ARTIFACT = "createArtifact"; + String OPERATION_UPDATE_ARTIFACT = "updateArtifact"; + String OPERATION_DELETE_ARTIFACT = "deleteArtifact"; + String OPERATION_GET_ARTIFACT_CONTENT = "getArtifactContent"; + String OPERATION_GET_ARTIFACT_METADATA = "getArtifactMetadata"; + String OPERATION_SEARCH_ARTIFACTS = "searchArtifacts"; + String OPERATION_LIST_VERSIONS = "listVersions"; + String OPERATION_CREATE_GROUP = "createGroup"; + String OPERATION_TEST_COMPATIBILITY = "testCompatibility"; + String OPERATION_VALIDATE = "validate"; +} diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java new file mode 100644 index 0000000000000..07842bfd796ff --- /dev/null +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java @@ -0,0 +1,99 @@ +/* + * 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.apicurioregistry; + +import java.io.InputStream; +import java.util.List; + +import io.apicurio.registry.rest.client.RegistryClient; +import io.apicurio.registry.rest.client.models.SearchedVersion; +import io.apicurio.registry.rest.client.models.VersionSearchResults; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.Processor; +import org.apache.camel.support.ScheduledPollConsumer; + +public class ApicurioRegistryConsumer extends ScheduledPollConsumer { + + private final ApicurioRegistryEndpoint endpoint; + private final ApicurioRegistryConfiguration configuration; + private volatile Long lastSeenGlobalId; + + public ApicurioRegistryConsumer(ApicurioRegistryEndpoint endpoint, Processor processor, + ApicurioRegistryConfiguration configuration) { + super(endpoint, processor); + this.endpoint = endpoint; + this.configuration = configuration; + } + + @Override + protected int poll() throws Exception { + String groupId = endpoint.getGroupId(); + String artifactId = endpoint.getArtifactId(); + + if (groupId == null || artifactId == null) { + throw new IllegalArgumentException( + "Both groupId and artifactId are required for the consumer"); + } + + RegistryClient client = endpoint.getRegistryClient(); + VersionSearchResults results = client.groups().byGroupId(groupId) + .artifacts().byArtifactId(artifactId).versions().get(); + + if (results == null || results.getVersions() == null) { + return 0; + } + + List versions = results.getVersions(); + int count = 0; + for (SearchedVersion version : versions) { + Long globalId = version.getGlobalId(); + if (lastSeenGlobalId == null || globalId > lastSeenGlobalId) { + Exchange exchange = createExchange(true); + Message message = exchange.getIn(); + + message.setHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + message.setHeader(ApicurioRegistryConstants.HEADER_VERSION, version.getVersion()); + message.setHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, globalId); + message.setHeader(ApicurioRegistryConstants.HEADER_CONTENT_ID, version.getContentId()); + message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, version.getArtifactType()); + if (version.getState() != null) { + message.setHeader(ApicurioRegistryConstants.HEADER_VERSION_STATE, + version.getState().getValue()); + } + + if (configuration.isFetchContent()) { + InputStream content = client.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions() + .byVersionExpression(version.getVersion()).content().get(); + message.setBody(content); + } else { + message.setBody(version); + } + + getProcessor().process(exchange); + + if (lastSeenGlobalId == null || globalId > lastSeenGlobalId) { + lastSeenGlobalId = globalId; + } + count++; + } + } + return count; + } +} diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java new file mode 100644 index 0000000000000..5d6b9dc464a14 --- /dev/null +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java @@ -0,0 +1,132 @@ +/* + * 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.apicurioregistry; + +import io.apicurio.registry.client.RegistryClientFactory; +import io.apicurio.registry.client.common.RegistryClientOptions; +import io.apicurio.registry.rest.client.RegistryClient; +import org.apache.camel.Category; +import org.apache.camel.Consumer; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.spi.EndpointServiceLocation; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriPath; +import org.apache.camel.support.ScheduledPollEndpoint; + +/** + * Manage artifacts, versions, and groups in Apicurio Registry v3. + */ +@UriEndpoint(firstVersion = "4.22.0", scheme = "apicurio-registry", title = "Apicurio Registry", + syntax = "apicurio-registry:groupId/artifactId", + category = { Category.CLOUD, Category.API }, headersClass = ApicurioRegistryConstants.class) +public class ApicurioRegistryEndpoint extends ScheduledPollEndpoint implements EndpointServiceLocation { + + @UriPath(description = "The artifact group ID") + private String groupId; + + @UriPath(description = "The artifact ID") + private String artifactId; + + @UriParam + private ApicurioRegistryConfiguration configuration; + + @UriParam(label = "advanced", description = "To use a pre-configured RegistryClient instance") + private RegistryClient registryClient; + + ApicurioRegistryEndpoint(String uri, ApicurioRegistryComponent component, + ApicurioRegistryConfiguration configuration, + String groupId, String artifactId) { + super(uri, component); + this.configuration = configuration; + this.groupId = groupId; + this.artifactId = artifactId; + } + + @Override + public Producer createProducer() throws Exception { + return new ApicurioRegistryProducer(this, configuration); + } + + @Override + public Consumer createConsumer(Processor processor) throws Exception { + ApicurioRegistryConsumer consumer = new ApicurioRegistryConsumer(this, processor, configuration); + configureConsumer(consumer); + return consumer; + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + if (registryClient == null) { + registryClient = createRegistryClient(); + } + } + + @Override + protected void doStop() throws Exception { + super.doStop(); + registryClient = null; + } + + private RegistryClient createRegistryClient() { + RegistryClientOptions options = RegistryClientOptions.create(configuration.getRegistryUrl()); + String authType = configuration.getAuthType(); + if ("basic".equalsIgnoreCase(authType)) { + options.basicAuth(configuration.getUsername(), configuration.getPassword()); + } else if ("oidc".equalsIgnoreCase(authType)) { + options.oauth2(configuration.getTokenEndpoint(), configuration.getClientId(), + configuration.getClientSecret(), configuration.getScope()); + } + return RegistryClientFactory.create(options); + } + + public RegistryClient getRegistryClient() { + return registryClient; + } + + public void setRegistryClient(RegistryClient registryClient) { + this.registryClient = registryClient; + } + + public String getGroupId() { + return groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public ApicurioRegistryConfiguration getConfiguration() { + return configuration; + } + + public void setConfiguration(ApicurioRegistryConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public String getServiceUrl() { + return configuration.getRegistryUrl(); + } + + @Override + public String getServiceProtocol() { + return "http"; + } +} diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java new file mode 100644 index 0000000000000..13b5face56ed1 --- /dev/null +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java @@ -0,0 +1,247 @@ +/* + * 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.apicurioregistry; + +import java.io.InputStream; + +import io.apicurio.registry.rest.client.RegistryClient; +import io.apicurio.registry.rest.client.models.ArtifactMetaData; +import io.apicurio.registry.rest.client.models.CreateArtifact; +import io.apicurio.registry.rest.client.models.CreateArtifactResponse; +import io.apicurio.registry.rest.client.models.CreateGroup; +import io.apicurio.registry.rest.client.models.CreateVersion; +import io.apicurio.registry.rest.client.models.GroupMetaData; +import io.apicurio.registry.rest.client.models.IfArtifactExists; +import io.apicurio.registry.rest.client.models.VersionContent; +import io.apicurio.registry.rest.client.models.VersionMetaData; +import io.apicurio.registry.rest.client.models.VersionSearchResults; +import org.apache.camel.Message; +import org.apache.camel.spi.InvokeOnHeader; +import org.apache.camel.support.HeaderSelectorProducer; + +public class ApicurioRegistryProducer extends HeaderSelectorProducer { + + private final ApicurioRegistryEndpoint endpoint; + private final ApicurioRegistryConfiguration configuration; + + public ApicurioRegistryProducer(ApicurioRegistryEndpoint endpoint, + ApicurioRegistryConfiguration configuration) { + super(endpoint, ApicurioRegistryConstants.HEADER_OPERATION, configuration::getOperation); + this.endpoint = endpoint; + this.configuration = configuration; + } + + private RegistryClient getClient() { + return endpoint.getRegistryClient(); + } + + private String resolveGroupId(Message message) { + String gid = message.getHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, String.class); + return gid != null ? gid : endpoint.getGroupId(); + } + + private String resolveArtifactId(Message message) { + String aid = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, String.class); + return aid != null ? aid : endpoint.getArtifactId(); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT) + public void createArtifact(Message message) { + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + String artifactType = message.getHeader( + ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, configuration.getArtifactType(), String.class); + String name = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_NAME, String.class); + String description = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_DESCRIPTION, String.class); + String content = message.getBody(String.class); + String contentType = message.getHeader( + ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); + String ifExistsVal = message.getHeader( + ApicurioRegistryConstants.HEADER_IF_EXISTS, configuration.getIfExists(), String.class); + + CreateArtifact createArtifact = new CreateArtifact(); + createArtifact.setArtifactId(artifactId); + createArtifact.setArtifactType(artifactType); + createArtifact.setName(name); + createArtifact.setDescription(description); + + if (content != null) { + CreateVersion firstVersion = new CreateVersion(); + VersionContent vc = new VersionContent(); + vc.setContent(content); + vc.setContentType(contentType); + firstVersion.setContent(vc); + createArtifact.setFirstVersion(firstVersion); + } + + CreateArtifactResponse result = getClient().groups().byGroupId(groupId).artifacts() + .post(createArtifact, config -> { + if (ifExistsVal != null) { + config.queryParameters.ifExists = IfArtifactExists.forValue(ifExistsVal); + } + }); + message.setBody(result); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_UPDATE_ARTIFACT) + public void updateArtifact(Message message) { + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + String content = message.getBody(String.class); + String version = message.getHeader(ApicurioRegistryConstants.HEADER_VERSION, String.class); + String contentType = message.getHeader( + ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); + + CreateVersion createVersion = new CreateVersion(); + createVersion.setVersion(version); + VersionContent vc = new VersionContent(); + vc.setContent(content); + vc.setContentType(contentType); + createVersion.setContent(vc); + + VersionMetaData result = getClient().groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().post(createVersion); + message.setBody(result); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_DELETE_ARTIFACT) + public void deleteArtifact(Message message) { + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + getClient().groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).delete(); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_GET_ARTIFACT_CONTENT) + public void getArtifactContent(Message message) { + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + String version = message.getHeader( + ApicurioRegistryConstants.HEADER_VERSION, "branch=latest", String.class); + + InputStream content = getClient().groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().byVersionExpression(version).content().get(); + message.setBody(content); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_GET_ARTIFACT_METADATA) + public void getArtifactMetadata(Message message) { + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + + ArtifactMetaData metadata = getClient().groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).get(); + message.setBody(metadata); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_SEARCH_ARTIFACTS) + public void searchArtifacts(Message message) { + var results = getClient().search().artifacts().get(config -> { + String name = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_NAME, String.class); + String groupId = resolveGroupId(message); + String description = message.getHeader( + ApicurioRegistryConstants.HEADER_ARTIFACT_DESCRIPTION, String.class); + if (name != null) { + config.queryParameters.name = name; + } + if (groupId != null) { + config.queryParameters.groupId = groupId; + } + if (description != null) { + config.queryParameters.description = description; + } + }); + message.setBody(results); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_LIST_VERSIONS) + public void listVersions(Message message) { + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + + VersionSearchResults results = getClient().groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().get(); + message.setBody(results); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_CREATE_GROUP) + public void createGroup(Message message) { + String groupId = resolveGroupId(message); + String description = message.getHeader( + ApicurioRegistryConstants.HEADER_ARTIFACT_DESCRIPTION, String.class); + + CreateGroup createGroup = new CreateGroup(); + createGroup.setGroupId(groupId); + createGroup.setDescription(description); + + GroupMetaData result = getClient().groups().post(createGroup); + message.setBody(result); + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_TEST_COMPATIBILITY) + public void testCompatibility(Message message) { + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + String content = message.getBody(String.class); + String contentType = message.getHeader( + ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); + + CreateVersion createVersion = new CreateVersion(); + VersionContent vc = new VersionContent(); + vc.setContent(content); + vc.setContentType(contentType); + createVersion.setContent(vc); + + try { + getClient().groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions() + .post(createVersion, config -> config.queryParameters.dryRun = true); + message.setBody(true); + } catch (Exception e) { + message.setBody(false); + message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, e.getMessage()); + } + } + + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_VALIDATE) + public void validate(Message message) throws Exception { + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + String content = message.getBody(String.class); + String contentType = message.getHeader( + ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); + + CreateVersion createVersion = new CreateVersion(); + VersionContent vc = new VersionContent(); + vc.setContent(content); + vc.setContentType(contentType); + createVersion.setContent(vc); + + try { + getClient().groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions() + .post(createVersion, config -> config.queryParameters.dryRun = true); + message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, true); + } catch (Exception e) { + message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, false); + message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, e.getMessage()); + if (configuration.isFailOnValidation()) { + throw new ApicurioRegistryValidationException( + "Validation failed for artifact " + groupId + "/" + artifactId, e.getMessage()); + } + } + } +} diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryValidationException.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryValidationException.java new file mode 100644 index 0000000000000..19855437cb093 --- /dev/null +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryValidationException.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.apicurioregistry; + +import org.apache.camel.CamelException; + +public class ApicurioRegistryValidationException extends CamelException { + + private final String validationErrors; + + public ApicurioRegistryValidationException(String message, String validationErrors) { + super(message); + this.validationErrors = validationErrors; + } + + public String getValidationErrors() { + return validationErrors; + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java new file mode 100644 index 0000000000000..70424783ce1b2 --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java @@ -0,0 +1,89 @@ +/* + * 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.apicurioregistry; + +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class ApicurioRegistryComponentTest extends CamelTestSupport { + + @Test + void testEndpointCreatedWithGroupAndArtifact() throws Exception { + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( + "apicurio-registry:myGroup/myArtifact?registryUrl=http://localhost:8080/apis/registry/v3"); + assertNotNull(endpoint); + assertEquals("myGroup", endpoint.getGroupId()); + assertEquals("myArtifact", endpoint.getArtifactId()); + assertEquals("http://localhost:8080/apis/registry/v3", endpoint.getConfiguration().getRegistryUrl()); + } + + @Test + void testEndpointCreatedWithGroupOnly() throws Exception { + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( + "apicurio-registry:myGroup?registryUrl=http://localhost:8080/apis/registry/v3"); + assertNotNull(endpoint); + assertEquals("myGroup", endpoint.getGroupId()); + assertNull(endpoint.getArtifactId()); + } + + @Test + void testEndpointWithAuthOptions() throws Exception { + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3" + + "&authType=basic&username=user&password=pass"); + assertNotNull(endpoint); + assertEquals("basic", endpoint.getConfiguration().getAuthType()); + assertEquals("user", endpoint.getConfiguration().getUsername()); + assertEquals("pass", endpoint.getConfiguration().getPassword()); + } + + @Test + void testEndpointWithOperationOption() throws Exception { + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3" + + "&operation=createArtifact"); + assertNotNull(endpoint); + assertEquals("createArtifact", endpoint.getConfiguration().getOperation()); + } + + @Test + void testConfigurationCopy() { + ApicurioRegistryConfiguration config = new ApicurioRegistryConfiguration(); + config.setRegistryUrl("http://localhost:8080"); + config.setOperation("createArtifact"); + config.setAuthType("basic"); + config.setUsername("user"); + + ApicurioRegistryConfiguration copy = config.copy(); + assertEquals(config.getRegistryUrl(), copy.getRegistryUrl()); + assertEquals(config.getOperation(), copy.getOperation()); + assertEquals(config.getAuthType(), copy.getAuthType()); + assertEquals(config.getUsername(), copy.getUsername()); + } + + @Test + void testEndpointServiceLocation() throws Exception { + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3"); + assertEquals("http://localhost:8080/apis/registry/v3", endpoint.getServiceUrl()); + assertEquals("http", endpoint.getServiceProtocol()); + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java new file mode 100644 index 0000000000000..0976f3abbea35 --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -0,0 +1,112 @@ +/* + * 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.apicurioregistry; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import io.apicurio.registry.rest.client.RegistryClient; +import io.apicurio.registry.rest.client.models.SearchedVersion; +import io.apicurio.registry.rest.client.models.VersionSearchResults; +import org.apache.camel.BindToRegistry; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ApicurioRegistryConsumerTest extends CamelTestSupport { + + private final RegistryClient mockClient = mock(RegistryClient.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); + + @BindToRegistry("apicurio-registry") + public ApicurioRegistryComponent getComponent() { + ApicurioRegistryComponent component = new ApicurioRegistryComponent(context); + component.getConfiguration().setRegistryUrl("http://localhost:8080/apis/registry/v3"); + return component; + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=500") + .to("mock:result"); + } + }; + } + + private void injectMockClient() { + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( + "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=500"); + endpoint.setRegistryClient(mockClient); + } + + @Test + void testPollNewVersions() throws Exception { + injectMockClient(); + + SearchedVersion v1 = new SearchedVersion(); + v1.setGlobalId(1L); + v1.setVersion("1.0"); + v1.setContentId(100L); + v1.setArtifactType("JSON"); + + SearchedVersion v2 = new SearchedVersion(); + v2.setGlobalId(2L); + v2.setVersion("2.0"); + v2.setContentId(101L); + v2.setArtifactType("JSON"); + + VersionSearchResults results = new VersionSearchResults(); + results.setVersions(List.of(v1, v2)); + + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().get()) + .thenReturn(results); + + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMinimumMessageCount(2); + MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); + } + + @Test + void testPollNoNewVersionsAfterInitial() throws Exception { + injectMockClient(); + + SearchedVersion v1 = new SearchedVersion(); + v1.setGlobalId(1L); + v1.setVersion("1.0"); + v1.setContentId(100L); + v1.setArtifactType("JSON"); + + VersionSearchResults results = new VersionSearchResults(); + results.setVersions(List.of(v1)); + + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().get()) + .thenReturn(results); + + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(1); + MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java new file mode 100644 index 0000000000000..4c76854d4323b --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java @@ -0,0 +1,209 @@ +/* + * 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.apicurioregistry; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import io.apicurio.registry.rest.client.RegistryClient; +import io.apicurio.registry.rest.client.models.ArtifactMetaData; +import io.apicurio.registry.rest.client.models.CreateArtifact; +import io.apicurio.registry.rest.client.models.CreateArtifactResponse; +import io.apicurio.registry.rest.client.models.CreateGroup; +import io.apicurio.registry.rest.client.models.CreateVersion; +import io.apicurio.registry.rest.client.models.GroupMetaData; +import io.apicurio.registry.rest.client.models.VersionMetaData; +import io.apicurio.registry.rest.client.models.VersionSearchResults; +import org.apache.camel.BindToRegistry; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ApicurioRegistryProducerTest extends CamelTestSupport { + + private final RegistryClient mockClient = mock(RegistryClient.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); + + @BindToRegistry("apicurio-registry") + public ApicurioRegistryComponent getComponent() { + ApicurioRegistryComponent component = new ApicurioRegistryComponent(context); + component.getConfiguration().setRegistryUrl("http://localhost:8080/apis/registry/v3"); + return component; + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:createArtifact") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=createArtifact"); + + from("direct:deleteArtifact") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=deleteArtifact"); + + from("direct:getArtifactMetadata") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=getArtifactMetadata"); + + from("direct:getArtifactContent") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=getArtifactContent"); + + from("direct:listVersions") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=listVersions"); + + from("direct:createGroup") + .to("apicurio-registry:newGroup?registryUrl=http://localhost:8080/apis/registry/v3&operation=createGroup"); + + from("direct:updateArtifact") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=updateArtifact"); + + from("direct:searchArtifacts") + .to("apicurio-registry:testGroup?registryUrl=http://localhost:8080/apis/registry/v3&operation=searchArtifacts"); + + from("direct:operationFromHeader") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3"); + } + }; + } + + private void injectMockClient(String endpointUri) throws Exception { + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint(endpointUri); + endpoint.setRegistryClient(mockClient); + } + + @Test + void testCreateArtifact() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=createArtifact"; + injectMockClient(endpointUri); + + CreateArtifactResponse mockResponse = new CreateArtifactResponse(); + when(mockClient.groups().byGroupId(anyString()).artifacts().post(any(CreateArtifact.class), any())) + .thenReturn(mockResponse); + + Object result = template.requestBody("direct:createArtifact", "{\"test\":true}"); + assertNotNull(result); + verify(mockClient.groups().byGroupId("testGroup").artifacts()).post(any(CreateArtifact.class), any()); + } + + @Test + void testDeleteArtifact() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=deleteArtifact"; + injectMockClient(endpointUri); + + template.sendBody("direct:deleteArtifact", null); + verify(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact")).delete(); + } + + @Test + void testGetArtifactMetadata() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=getArtifactMetadata"; + injectMockClient(endpointUri); + + ArtifactMetaData mockMetadata = new ArtifactMetaData(); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact").get()) + .thenReturn(mockMetadata); + + Object result = template.requestBody("direct:getArtifactMetadata", (Object) null); + assertEquals(mockMetadata, result); + } + + @Test + void testGetArtifactContent() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=getArtifactContent"; + injectMockClient(endpointUri); + + ByteArrayInputStream mockContent = new ByteArrayInputStream("{\"test\":true}".getBytes(StandardCharsets.UTF_8)); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("branch=latest").content().get()) + .thenReturn(mockContent); + + Object result = template.requestBody("direct:getArtifactContent", (Object) null); + assertNotNull(result); + } + + @Test + void testListVersions() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=listVersions"; + injectMockClient(endpointUri); + + VersionSearchResults mockResults = new VersionSearchResults(); + mockResults.setVersions(List.of()); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().get()) + .thenReturn(mockResults); + + Object result = template.requestBody("direct:listVersions", (Object) null); + assertEquals(mockResults, result); + } + + @Test + void testCreateGroup() throws Exception { + String endpointUri + = "apicurio-registry:newGroup?registryUrl=http://localhost:8080/apis/registry/v3&operation=createGroup"; + injectMockClient(endpointUri); + + GroupMetaData mockGroupMeta = new GroupMetaData(); + when(mockClient.groups().post(any(CreateGroup.class))).thenReturn(mockGroupMeta); + + Object result = template.requestBody("direct:createGroup", (Object) null); + assertEquals(mockGroupMeta, result); + } + + @Test + void testUpdateArtifact() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=updateArtifact"; + injectMockClient(endpointUri); + + VersionMetaData mockVersionMeta = new VersionMetaData(); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().post(any(CreateVersion.class))) + .thenReturn(mockVersionMeta); + + Object result = template.requestBody("direct:updateArtifact", "{\"updated\":true}"); + assertEquals(mockVersionMeta, result); + } + + @Test + void testOperationFromHeader() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3"; + injectMockClient(endpointUri); + + ArtifactMetaData mockMetadata = new ArtifactMetaData(); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact").get()) + .thenReturn(mockMetadata); + + Object result = template.requestBodyAndHeader("direct:operationFromHeader", null, + ApicurioRegistryConstants.HEADER_OPERATION, "getArtifactMetadata"); + assertEquals(mockMetadata, result); + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java new file mode 100644 index 0000000000000..2eeb421dc4b59 --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java @@ -0,0 +1,95 @@ +/* + * 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.apicurioregistry.integration; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants; +import org.apache.camel.component.mock.MockEndpoint; +import org.junit.jupiter.api.Test; + +class ApicurioRegistryConsumerIT extends ApicurioRegistryTestSupport { + + private static final String JSON_SCHEMA = """ + { + "type": "object", + "properties": { + "name": {"type": "string"} + } + } + """; + + private final String groupId = "consumer-test-" + UUID.randomUUID(); + private final String artifactId = "consumer-artifact-" + UUID.randomUUID(); + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + // Producer route to create artifacts + from("direct:createForConsumer") + .toD("apicurio-registry:${header.CamelApicurioRegistryGroupId}/${header.CamelApicurioRegistryArtifactId}" + + "?registryUrl=" + getRegistryUrl()); + + // Consumer route + from("apicurio-registry:" + groupId + "/" + artifactId + + "?registryUrl=" + getRegistryUrl() + "&delay=1000") + .to("mock:consumed"); + } + }; + } + + @Test + void testConsumerReceivesNewVersions() throws Exception { + // Create the group first + Map groupHeaders = new HashMap<>(); + groupHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_GROUP); + groupHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + template.request("direct:createForConsumer", exchange -> exchange.getIn().setHeaders(groupHeaders)); + + // Create the artifact + Map createHeaders = new HashMap<>(); + createHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT); + createHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "JSON"); + createHeaders.put(ApicurioRegistryConstants.HEADER_IF_EXISTS, "FIND_OR_CREATE_VERSION"); + template.request("direct:createForConsumer", exchange -> { + exchange.getIn().setHeaders(createHeaders); + exchange.getIn().setBody(JSON_SCHEMA); + }); + + MockEndpoint mock = getMockEndpoint("mock:consumed"); + mock.expectedMinimumMessageCount(1); + MockEndpoint.assertIsSatisfied(context, 30, TimeUnit.SECONDS); + + // Verify headers set by consumer + Map receivedHeaders = mock.getReceivedExchanges().get(0).getIn().getHeaders(); + assertEquals(groupId, receivedHeaders.get(ApicurioRegistryConstants.HEADER_GROUP_ID)); + assertEquals(artifactId, receivedHeaders.get(ApicurioRegistryConstants.HEADER_ARTIFACT_ID)); + } + + private void assertEquals(Object expected, Object actual) { + org.junit.jupiter.api.Assertions.assertEquals(expected, actual); + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java new file mode 100644 index 0000000000000..1cab39009d65e --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java @@ -0,0 +1,212 @@ +/* + * 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.apicurioregistry.integration; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import io.apicurio.registry.rest.client.models.ArtifactMetaData; +import io.apicurio.registry.rest.client.models.CreateArtifactResponse; +import io.apicurio.registry.rest.client.models.GroupMetaData; +import io.apicurio.registry.rest.client.models.VersionMetaData; +import io.apicurio.registry.rest.client.models.VersionSearchResults; +import org.apache.camel.Exchange; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ApicurioRegistryProducerIT extends ApicurioRegistryTestSupport { + + private static final String JSON_SCHEMA = """ + { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"} + } + } + """; + + private static final String JSON_SCHEMA_V2 = """ + { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "email": {"type": "string"} + } + } + """; + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:operation") + .toD("apicurio-registry:${header.CamelApicurioRegistryGroupId}/${header.CamelApicurioRegistryArtifactId}" + + "?registryUrl=" + getRegistryUrl()); + } + }; + } + + @Test + void testFullArtifactLifecycle() throws Exception { + String groupId = "test-group-" + UUID.randomUUID(); + String artifactId = "test-artifact-" + UUID.randomUUID(); + + // 1. Create group + Map headers = new HashMap<>(); + headers.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_GROUP); + headers.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + Exchange groupResult = template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(headers); + }); + assertNotNull(groupResult.getIn().getBody()); + assertInstanceOf(GroupMetaData.class, groupResult.getIn().getBody()); + + // 2. Create artifact + Map createHeaders = new HashMap<>(); + createHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT); + createHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "JSON"); + createHeaders.put(ApicurioRegistryConstants.HEADER_IF_EXISTS, "FIND_OR_CREATE_VERSION"); + Exchange createResult = template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(createHeaders); + exchange.getIn().setBody(JSON_SCHEMA); + }); + assertNotNull(createResult.getIn().getBody()); + assertInstanceOf(CreateArtifactResponse.class, createResult.getIn().getBody()); + + // 3. Get artifact metadata + Map getMetaHeaders = new HashMap<>(); + getMetaHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, + ApicurioRegistryConstants.OPERATION_GET_ARTIFACT_METADATA); + getMetaHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + getMetaHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + Exchange metaResult = template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(getMetaHeaders); + }); + assertInstanceOf(ArtifactMetaData.class, metaResult.getIn().getBody()); + ArtifactMetaData metadata = (ArtifactMetaData) metaResult.getIn().getBody(); + assertEquals(artifactId, metadata.getArtifactId()); + + // 4. Get artifact content + Map getContentHeaders = new HashMap<>(); + getContentHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, + ApicurioRegistryConstants.OPERATION_GET_ARTIFACT_CONTENT); + getContentHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + getContentHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + Exchange contentResult = template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(getContentHeaders); + }); + Object contentBody = contentResult.getIn().getBody(); + assertNotNull(contentBody); + String contentStr; + if (contentBody instanceof InputStream) { + contentStr = new String(((InputStream) contentBody).readAllBytes(), StandardCharsets.UTF_8); + } else { + contentStr = contentBody.toString(); + } + assertTrue(contentStr.contains("name")); + + // 5. Update artifact (new version) + Map updateHeaders = new HashMap<>(); + updateHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, + ApicurioRegistryConstants.OPERATION_UPDATE_ARTIFACT); + updateHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + updateHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + Exchange updateResult = template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(updateHeaders); + exchange.getIn().setBody(JSON_SCHEMA_V2); + }); + assertInstanceOf(VersionMetaData.class, updateResult.getIn().getBody()); + + // 6. List versions + Map listHeaders = new HashMap<>(); + listHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, + ApicurioRegistryConstants.OPERATION_LIST_VERSIONS); + listHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + listHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + Exchange listResult = template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(listHeaders); + }); + assertInstanceOf(VersionSearchResults.class, listResult.getIn().getBody()); + VersionSearchResults versions = (VersionSearchResults) listResult.getIn().getBody(); + assertTrue(versions.getCount() >= 2); + + // 7. Delete artifact + Map deleteHeaders = new HashMap<>(); + deleteHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, + ApicurioRegistryConstants.OPERATION_DELETE_ARTIFACT); + deleteHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + deleteHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(deleteHeaders); + }); + } + + @Test + void testSearchArtifacts() throws Exception { + String groupId = "search-group-" + UUID.randomUUID(); + String artifactId = "search-artifact-" + UUID.randomUUID(); + + // Create group first + Map groupHeaders = new HashMap<>(); + groupHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_GROUP); + groupHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + template.request("direct:operation", exchange -> exchange.getIn().setHeaders(groupHeaders)); + + // Create artifact + Map createHeaders = new HashMap<>(); + createHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT); + createHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "JSON"); + template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(createHeaders); + exchange.getIn().setBody(JSON_SCHEMA); + }); + + // Search + Map searchHeaders = new HashMap<>(); + searchHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_SEARCH_ARTIFACTS); + searchHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + searchHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + Exchange searchResult = template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(searchHeaders); + }); + assertNotNull(searchResult.getIn().getBody()); + + // Cleanup + Map deleteHeaders = new HashMap<>(); + deleteHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_DELETE_ARTIFACT); + deleteHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + deleteHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + template.request("direct:operation", exchange -> exchange.getIn().setHeaders(deleteHeaders)); + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryTestSupport.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryTestSupport.java new file mode 100644 index 0000000000000..f79a08f63f50c --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryTestSupport.java @@ -0,0 +1,41 @@ +/* + * 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.apicurioregistry.integration; + +import org.apache.camel.BindToRegistry; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent; +import org.apache.camel.test.infra.apicurio.registry.services.ApicurioRegistryService; +import org.apache.camel.test.infra.apicurio.registry.services.ApicurioRegistryServiceFactory; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class ApicurioRegistryTestSupport extends CamelTestSupport { + + @RegisterExtension + public static ApicurioRegistryService service = ApicurioRegistryServiceFactory.createService(); + + @BindToRegistry("apicurio-registry") + public ApicurioRegistryComponent getApicurioRegistryComponent() { + ApicurioRegistryComponent component = new ApicurioRegistryComponent(); + component.getConfiguration().setRegistryUrl(service.getApicurioRegistryUrl()); + return component; + } + + protected String getRegistryUrl() { + return service.getApicurioRegistryUrl(); + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java new file mode 100644 index 0000000000000..c615eb8321831 --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java @@ -0,0 +1,132 @@ +/* + * 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.apicurioregistry.integration; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.camel.Exchange; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class ApicurioRegistryValidateIT extends ApicurioRegistryTestSupport { + + private static final String JSON_SCHEMA = """ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"} + }, + "required": ["name"] + } + """; + + private final String groupId = "validate-group-" + UUID.randomUUID(); + private final String artifactId = "validate-artifact-" + UUID.randomUUID(); + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:setup") + .toD("apicurio-registry:${header.CamelApicurioRegistryGroupId}/${header.CamelApicurioRegistryArtifactId}" + + "?registryUrl=" + getRegistryUrl()); + + from("direct:validate") + .to("apicurio-registry:" + groupId + "/" + artifactId + + "?registryUrl=" + getRegistryUrl() + + "&operation=validate&failOnValidation=false"); + + from("direct:testCompatibility") + .to("apicurio-registry:" + groupId + "/" + artifactId + + "?registryUrl=" + getRegistryUrl() + + "&operation=testCompatibility"); + } + }; + } + + @BeforeEach + void setupArtifact() { + // Create group + Map groupHeaders = new HashMap<>(); + groupHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_GROUP); + groupHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + template.request("direct:setup", exchange -> exchange.getIn().setHeaders(groupHeaders)); + + // Create artifact with JSON Schema + Map createHeaders = new HashMap<>(); + createHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT); + createHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "JSON"); + createHeaders.put(ApicurioRegistryConstants.HEADER_IF_EXISTS, "FIND_OR_CREATE_VERSION"); + template.request("direct:setup", exchange -> { + exchange.getIn().setHeaders(createHeaders); + exchange.getIn().setBody(JSON_SCHEMA); + }); + } + + @Test + void testValidateCompatibleContent() { + String compatibleSchema = """ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "email": {"type": "string"} + }, + "required": ["name"] + } + """; + + Exchange result = template.request("direct:validate", exchange -> { + exchange.getIn().setBody(compatibleSchema); + }); + + assertNotNull(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT)); + } + + @Test + void testTestCompatibility() { + String compatibleSchema = """ + { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"} + } + } + """; + + Exchange result = template.request("direct:testCompatibility", exchange -> { + exchange.getIn().setBody(compatibleSchema); + }); + + assertNotNull(result.getIn().getBody()); + } +} diff --git a/components/pom.xml b/components/pom.xml index af716a407a8cb..6ec84729bcad7 100644 --- a/components/pom.xml +++ b/components/pom.xml @@ -82,6 +82,7 @@ camel-ai camel-alibaba camel-amqp + camel-apicurio-registry camel-arangodb camel-as2 camel-asn1 diff --git a/parent/pom.xml b/parent/pom.xml index b28e6fb41e1d3..b5b3020552078 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -854,6 +854,11 @@ camel-amqp ${project.version} + + org.apache.camel + camel-apicurio-registry + ${project.version} + org.apache.camel camel-arangodb diff --git a/test-infra/camel-test-infra-apicurio-registry/pom.xml b/test-infra/camel-test-infra-apicurio-registry/pom.xml new file mode 100644 index 0000000000000..5114b2b8f7e36 --- /dev/null +++ b/test-infra/camel-test-infra-apicurio-registry/pom.xml @@ -0,0 +1,41 @@ + + + + + camel-test-infra-parent + org.apache.camel + ../camel-test-infra-parent/pom.xml + 4.22.0-SNAPSHOT + + + 4.0.0 + + camel-test-infra-apicurio-registry + Camel :: Test Infra :: Apicurio Registry + + + + org.apache.camel + camel-test-infra-common + ${project.version} + + + + diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/common/ApicurioRegistryProperties.java b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/common/ApicurioRegistryProperties.java new file mode 100644 index 0000000000000..d73943ed709d7 --- /dev/null +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/common/ApicurioRegistryProperties.java @@ -0,0 +1,28 @@ +/* + * 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.test.infra.apicurio.registry.common; + +public final class ApicurioRegistryProperties { + public static final String APICURIO_REGISTRY_URL = "apicurio.registry.url"; + public static final String APICURIO_REGISTRY_HOST = "apicurio.registry.host"; + public static final String APICURIO_REGISTRY_PORT = "apicurio.registry.port"; + public static final String APICURIO_REGISTRY_CONTAINER = "apicurio.registry.container"; + public static final int DEFAULT_PORT = 8080; + + private ApicurioRegistryProperties() { + } +} diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryInfraService.java b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryInfraService.java new file mode 100644 index 0000000000000..7a8b8380ff79a --- /dev/null +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryInfraService.java @@ -0,0 +1,28 @@ +/* + * 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.test.infra.apicurio.registry.services; + +import org.apache.camel.test.infra.common.services.InfrastructureService; + +public interface ApicurioRegistryInfraService extends InfrastructureService { + + String getApicurioRegistryUrl(); + + String host(); + + int port(); +} diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java new file mode 100644 index 0000000000000..e893e1875d598 --- /dev/null +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java @@ -0,0 +1,104 @@ +/* + * 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.test.infra.apicurio.registry.services; + +import org.apache.camel.spi.annotations.InfraService; +import org.apache.camel.test.infra.apicurio.registry.common.ApicurioRegistryProperties; +import org.apache.camel.test.infra.common.LocalPropertyResolver; +import org.apache.camel.test.infra.common.services.ContainerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +@InfraService(service = ApicurioRegistryInfraService.class, + description = "Apicurio Registry is an API/Schema registry", + serviceAlias = { "apicurio-registry" }) +public class ApicurioRegistryLocalContainerInfraService + implements ApicurioRegistryInfraService, ContainerService> { + + public static final String CONTAINER_NAME = "apicurio-registry"; + + private static final Logger LOG = LoggerFactory.getLogger(ApicurioRegistryLocalContainerInfraService.class); + + private final GenericContainer container; + + public ApicurioRegistryLocalContainerInfraService() { + this(LocalPropertyResolver.getProperty( + ApicurioRegistryLocalContainerInfraService.class, + ApicurioRegistryProperties.APICURIO_REGISTRY_CONTAINER)); + } + + public ApicurioRegistryLocalContainerInfraService(String containerName) { + container = initContainer(containerName, CONTAINER_NAME); + } + + public ApicurioRegistryLocalContainerInfraService(GenericContainer container) { + this.container = container; + } + + @SuppressWarnings("resource") + protected GenericContainer initContainer(String imageName, String containerName) { + return new GenericContainer<>(imageName) + .withNetworkAliases(containerName) + .withExposedPorts(ApicurioRegistryProperties.DEFAULT_PORT) + .waitingFor(Wait.forHttp("/health").forPort(ApicurioRegistryProperties.DEFAULT_PORT).forStatusCode(200)); + } + + @Override + public void registerProperties() { + System.setProperty(ApicurioRegistryProperties.APICURIO_REGISTRY_URL, getApicurioRegistryUrl()); + System.setProperty(ApicurioRegistryProperties.APICURIO_REGISTRY_HOST, host()); + System.setProperty(ApicurioRegistryProperties.APICURIO_REGISTRY_PORT, String.valueOf(port())); + } + + @Override + public void initialize() { + LOG.info("Trying to start the Apicurio Registry container"); + container.start(); + + registerProperties(); + LOG.info("Apicurio Registry instance running at {}", getApicurioRegistryUrl()); + } + + @Override + public void shutdown() { + LOG.info("Stopping the Apicurio Registry container"); + container.stop(); + } + + @Override + public GenericContainer getContainer() { + return container; + } + + @Override + public String getApicurioRegistryUrl() { + return String.format("http://%s:%d/apis/registry/v3", host(), + container.getMappedPort(ApicurioRegistryProperties.DEFAULT_PORT)); + } + + @Override + public String host() { + return container.getHost(); + } + + @Override + public int port() { + return container.getMappedPort(ApicurioRegistryProperties.DEFAULT_PORT); + } +} diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryRemoteInfraService.java b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryRemoteInfraService.java new file mode 100644 index 0000000000000..694a3ef14cbee --- /dev/null +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryRemoteInfraService.java @@ -0,0 +1,58 @@ +/* + * 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.test.infra.apicurio.registry.services; + +import org.apache.camel.test.infra.apicurio.registry.common.ApicurioRegistryProperties; + +public class ApicurioRegistryRemoteInfraService implements ApicurioRegistryInfraService { + + @Override + public void registerProperties() { + // NO-OP + } + + @Override + public void initialize() { + registerProperties(); + } + + @Override + public void shutdown() { + // NO-OP + } + + @Override + public String getApicurioRegistryUrl() { + return System.getProperty(ApicurioRegistryProperties.APICURIO_REGISTRY_URL); + } + + @Override + public String host() { + return System.getProperty(ApicurioRegistryProperties.APICURIO_REGISTRY_HOST); + } + + @Override + public int port() { + String strPort = System.getProperty(ApicurioRegistryProperties.APICURIO_REGISTRY_PORT); + + if (strPort == null) { + return ApicurioRegistryProperties.DEFAULT_PORT; + } + + return Integer.parseInt(strPort); + } +} diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryService.java b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryService.java new file mode 100644 index 0000000000000..71969410a917e --- /dev/null +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryService.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.test.infra.apicurio.registry.services; + +import org.apache.camel.test.infra.common.services.ContainerTestService; +import org.apache.camel.test.infra.common.services.TestService; + +public interface ApicurioRegistryService extends ApicurioRegistryInfraService, TestService, ContainerTestService { +} diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryServiceFactory.java b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryServiceFactory.java new file mode 100644 index 0000000000000..2a64be01f9aaa --- /dev/null +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryServiceFactory.java @@ -0,0 +1,46 @@ +/* + * 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.test.infra.apicurio.registry.services; + +import org.apache.camel.test.infra.common.services.SimpleTestServiceBuilder; + +public final class ApicurioRegistryServiceFactory { + + private ApicurioRegistryServiceFactory() { + } + + public static SimpleTestServiceBuilder builder() { + return new SimpleTestServiceBuilder<>("apicurio-registry"); + } + + public static ApicurioRegistryService createService() { + return builder() + .addLocalMapping(ApicurioRegistryLocalContainerTestService::new) + .addRemoteMapping(ApicurioRegistryRemoteTestService::new) + .build(); + } + + public static class ApicurioRegistryLocalContainerTestService + extends ApicurioRegistryLocalContainerInfraService + implements ApicurioRegistryService { + } + + public static class ApicurioRegistryRemoteTestService + extends ApicurioRegistryRemoteInfraService + implements ApicurioRegistryService { + } +} diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties b/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties new file mode 100644 index 0000000000000..fd927dc86382c --- /dev/null +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +apicurio.registry.container=quay.io/apicurio/apicurio-registry:3.0.6 diff --git a/test-infra/pom.xml b/test-infra/pom.xml index d67821fa64944..2ffc2452d5e65 100644 --- a/test-infra/pom.xml +++ b/test-infra/pom.xml @@ -59,6 +59,7 @@ camel-test-infra-dispatch-router camel-test-infra-jdbc camel-test-infra-arangodb + camel-test-infra-apicurio-registry camel-test-infra-consul camel-test-infra-google-pubsub camel-test-infra-infinispan From 2a470b40962b5d1a93a4c041c75909b036370a3f Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Fri, 17 Jul 2026 13:17:40 +0200 Subject: [PATCH 02/13] Use Apicurio Registry 3.3.0 container image for ITs Assisted by Claude, model: claude-opus-4-6 --- .../services/ApicurioRegistryLocalContainerInfraService.java | 3 ++- .../test/infra/apicurio/registry/services/container.properties | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java index e893e1875d598..103e6403297c1 100644 --- a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java @@ -56,7 +56,8 @@ protected GenericContainer initContainer(String imageName, String containerNa return new GenericContainer<>(imageName) .withNetworkAliases(containerName) .withExposedPorts(ApicurioRegistryProperties.DEFAULT_PORT) - .waitingFor(Wait.forHttp("/health").forPort(ApicurioRegistryProperties.DEFAULT_PORT).forStatusCode(200)); + .waitingFor(Wait.forHttp("/apis/registry/v3/system/info") + .forPort(ApicurioRegistryProperties.DEFAULT_PORT).forStatusCode(200)); } @Override diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties b/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties index fd927dc86382c..a153fd2fe64c6 100644 --- a/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties @@ -14,4 +14,4 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## --------------------------------------------------------------------------- -apicurio.registry.container=quay.io/apicurio/apicurio-registry:3.0.6 +apicurio.registry.container=quay.io/apicurio/apicurio-registry:3.3.0 From 6792a0469336b726198d2909ab58619e592aa86c Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Wed, 19 Aug 2026 13:23:35 +0200 Subject: [PATCH 03/13] Address review feedback on camel-apicurio-registry - Remove unused cacheTtl option and HEADER_DRY_RUN constant - Sort versions by globalId before processing in consumer - Convert InputStream to byte[] in getArtifactContent to avoid leaks - Extract shared doDryRun helper for testCompatibility/validate - Catch only ApiException in dry-run operations, rethrow others - Move apicurio-registry-sdk-version property to parent/pom.xml - Document consumer first-start replay behavior in .adoc - Add unit tests for searchArtifacts, testCompatibility, validate - Add consumer unit test for out-of-order globalId sorting - Improve validate IT assertions Assisted by Claude, model: claude-opus-4-6 --- components/camel-apicurio-registry/pom.xml | 4 - .../ApicurioRegistryComponentConfigurer.java | 6 - .../ApicurioRegistryEndpointConfigurer.java | 6 - .../ApicurioRegistryEndpointUriFactory.java | 3 +- .../apicurioregistry/apicurio-registry.json | 91 ++++++++------- .../docs/apicurio-registry-component.adoc | 4 + .../ApicurioRegistryConfiguration.java | 13 --- .../ApicurioRegistryConstants.java | 3 - .../ApicurioRegistryConsumer.java | 18 +-- .../ApicurioRegistryProducer.java | 58 +++++----- .../ApicurioRegistryConsumerTest.java | 41 +++++++ .../ApicurioRegistryProducerTest.java | 104 ++++++++++++++++++ .../ApicurioRegistryConsumerIT.java | 5 +- .../ApicurioRegistryValidateIT.java | 5 +- parent/pom.xml | 1 + 15 files changed, 235 insertions(+), 127 deletions(-) diff --git a/components/camel-apicurio-registry/pom.xml b/components/camel-apicurio-registry/pom.xml index 6745134f6042c..616536bfd200d 100644 --- a/components/camel-apicurio-registry/pom.xml +++ b/components/camel-apicurio-registry/pom.xml @@ -33,10 +33,6 @@ Camel :: Apicurio Registry Camel Apicurio Registry component - - 3.3.0 - - diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java index 4f83f5274502f..f994d7b5f094e 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java @@ -38,8 +38,6 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; case "bridgeerrorhandler": case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; - case "cachettl": - case "cacheTtl": getOrCreateConfiguration(target).setCacheTtl(property(camelContext, long.class, value)); return true; case "clientid": case "clientId": getOrCreateConfiguration(target).setClientId(property(camelContext, java.lang.String.class, value)); return true; case "clientsecret": @@ -76,8 +74,6 @@ public Class getOptionType(String name, boolean ignoreCase) { case "autowiredEnabled": return boolean.class; case "bridgeerrorhandler": case "bridgeErrorHandler": return boolean.class; - case "cachettl": - case "cacheTtl": return long.class; case "clientid": case "clientId": return java.lang.String.class; case "clientsecret": @@ -115,8 +111,6 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "autowiredEnabled": return target.isAutowiredEnabled(); case "bridgeerrorhandler": case "bridgeErrorHandler": return target.isBridgeErrorHandler(); - case "cachettl": - case "cacheTtl": return getOrCreateConfiguration(target).getCacheTtl(); case "clientid": case "clientId": return getOrCreateConfiguration(target).getClientId(); case "clientsecret": diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java index e4916994a870b..a82585a675887 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java @@ -35,8 +35,6 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "backoffMultiplier": target.setBackoffMultiplier(property(camelContext, int.class, value)); return true; case "bridgeerrorhandler": case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; - case "cachettl": - case "cacheTtl": target.getConfiguration().setCacheTtl(property(camelContext, long.class, value)); return true; case "clientid": case "clientId": target.getConfiguration().setClientId(property(camelContext, java.lang.String.class, value)); return true; case "clientsecret": @@ -105,8 +103,6 @@ public Class getOptionType(String name, boolean ignoreCase) { case "backoffMultiplier": return int.class; case "bridgeerrorhandler": case "bridgeErrorHandler": return boolean.class; - case "cachettl": - case "cacheTtl": return long.class; case "clientid": case "clientId": return java.lang.String.class; case "clientsecret": @@ -176,8 +172,6 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "backoffMultiplier": return target.getBackoffMultiplier(); case "bridgeerrorhandler": case "bridgeErrorHandler": return target.isBridgeErrorHandler(); - case "cachettl": - case "cacheTtl": return target.getConfiguration().getCacheTtl(); case "clientid": case "clientId": return target.getConfiguration().getClientId(); case "clientsecret": diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java index 90577aab7f31e..e2db8a74a3fca 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java @@ -24,7 +24,7 @@ public class ApicurioRegistryEndpointUriFactory extends org.apache.camel.support private static final Set ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(37); + Set props = new HashSet<>(36); props.add("artifactId"); props.add("artifactType"); props.add("authType"); @@ -32,7 +32,6 @@ public class ApicurioRegistryEndpointUriFactory extends org.apache.camel.support props.add("backoffIdleThreshold"); props.add("backoffMultiplier"); props.add("bridgeErrorHandler"); - props.add("cacheTtl"); props.add("clientId"); props.add("clientSecret"); props.add("delay"); diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json index 82bea993998e5..13430aac6a642 100644 --- a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json @@ -28,20 +28,19 @@ "bridgeErrorHandler": { "index": 1, "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." }, "fetchContent": { "index": 2, "kind": "property", "displayName": "Fetch Content", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to fetch the content for each new version found by the consumer" }, "artifactType": { "index": 3, "kind": "property", "displayName": "Artifact Type", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "JSON", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The default artifact type" }, - "cacheTtl": { "index": 4, "kind": "property", "displayName": "Cache Ttl", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 300000, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Schema cache TTL in milliseconds for the validate operation. 0 means no caching." }, - "failOnValidation": { "index": 5, "kind": "property", "displayName": "Fail On Validation", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead." }, - "ifExists": { "index": 6, "kind": "property", "displayName": "If Exists", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "FAIL", "CREATE_VERSION", "FIND_OR_CREATE_VERSION" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "FAIL", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Behavior when artifact already exists" }, - "lazyStartProducer": { "index": 7, "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." }, - "operation": { "index": 8, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, - "autowiredEnabled": { "index": 9, "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." }, - "configuration": { "index": 10, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, - "authType": { "index": 11, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 12, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, - "clientSecret": { "index": 13, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, - "password": { "index": 14, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, - "scope": { "index": 15, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, - "tokenEndpoint": { "index": 16, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 17, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "failOnValidation": { "index": 4, "kind": "property", "displayName": "Fail On Validation", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead." }, + "ifExists": { "index": 5, "kind": "property", "displayName": "If Exists", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "FAIL", "CREATE_VERSION", "FIND_OR_CREATE_VERSION" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "FAIL", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Behavior when artifact already exists" }, + "lazyStartProducer": { "index": 6, "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." }, + "operation": { "index": 7, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, + "autowiredEnabled": { "index": 8, "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." }, + "configuration": { "index": 9, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, + "authType": { "index": 10, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 11, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 12, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 13, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 14, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "tokenEndpoint": { "index": 15, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 16, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } }, "headers": { "CamelApicurioRegistryOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_OPERATION" }, @@ -53,12 +52,11 @@ "CamelApicurioRegistryArtifactDescription": { "index": 6, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact description", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_DESCRIPTION" }, "CamelApicurioRegistryIfExists": { "index": 7, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Behavior when artifact already exists (FAIL, CREATE_VERSION, FIND_OR_CREATE_VERSION)", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_IF_EXISTS" }, "CamelApicurioRegistryContentType": { "index": 8, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type of the artifact", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_TYPE" }, - "CamelApicurioRegistryDryRun": { "index": 9, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the operation is a dry run", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_DRY_RUN" }, - "CamelApicurioRegistryGlobalId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version global ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_GLOBAL_ID" }, - "CamelApicurioRegistryContentId": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, - "CamelApicurioRegistryVersionState": { "index": 12, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, - "CamelApicurioRegistryValidationResult": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether validation passed", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, - "CamelApicurioRegistryValidationErrors": { "index": 14, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Validation error details", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } + "CamelApicurioRegistryGlobalId": { "index": 9, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version global ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_GLOBAL_ID" }, + "CamelApicurioRegistryContentId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, + "CamelApicurioRegistryVersionState": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, + "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether validation passed", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, + "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Validation error details", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } }, "properties": { "groupId": { "index": 0, "kind": "path", "displayName": "Group Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact group ID" }, @@ -71,32 +69,31 @@ "exchangePattern": { "index": 7, "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." }, "pollStrategy": { "index": 8, "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." }, "artifactType": { "index": 9, "kind": "parameter", "displayName": "Artifact Type", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "JSON", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The default artifact type" }, - "cacheTtl": { "index": 10, "kind": "parameter", "displayName": "Cache Ttl", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "long", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 300000, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Schema cache TTL in milliseconds for the validate operation. 0 means no caching." }, - "failOnValidation": { "index": 11, "kind": "parameter", "displayName": "Fail On Validation", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead." }, - "ifExists": { "index": 12, "kind": "parameter", "displayName": "If Exists", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "FAIL", "CREATE_VERSION", "FIND_OR_CREATE_VERSION" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "FAIL", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Behavior when artifact already exists" }, - "operation": { "index": 13, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, - "lazyStartProducer": { "index": 14, "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." }, - "registryClient": { "index": 15, "kind": "parameter", "displayName": "Registry Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.apicurio.registry.rest.client.RegistryClient", "deprecated": false, "autowired": false, "secret": false, "description": "To use a pre-configured RegistryClient instance" }, - "backoffErrorThreshold": { "index": 16, "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": 17, "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": 18, "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": 19, "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": 20, "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": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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": 26, "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": 27, "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": 28, "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": 29, "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." }, - "authType": { "index": 30, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 31, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, - "clientSecret": { "index": 32, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, - "password": { "index": 33, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, - "scope": { "index": 34, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, - "tokenEndpoint": { "index": 35, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 36, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "failOnValidation": { "index": 10, "kind": "parameter", "displayName": "Fail On Validation", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead." }, + "ifExists": { "index": 11, "kind": "parameter", "displayName": "If Exists", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "FAIL", "CREATE_VERSION", "FIND_OR_CREATE_VERSION" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "FAIL", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Behavior when artifact already exists" }, + "operation": { "index": 12, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, + "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." }, + "registryClient": { "index": 14, "kind": "parameter", "displayName": "Registry Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.apicurio.registry.rest.client.RegistryClient", "deprecated": false, "autowired": false, "secret": false, "description": "To use a pre-configured RegistryClient instance" }, + "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." }, + "authType": { "index": 29, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 30, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 31, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 32, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 33, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "tokenEndpoint": { "index": 34, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 35, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } } } diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc index 950f4c6e8c3c9..2759669156c04 100644 --- a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -73,6 +73,10 @@ The consumer polls the registry for new versions of the specified artifact. It t seen `globalId` and only delivers new versions. Set the `delay` option to control the polling interval (default: 500ms). +On first start, all existing versions of the artifact are emitted as exchanges. Subsequent polls +only deliver versions created after the consumer started. The watermark is held in memory and +resets on route/context restart. + When `fetchContent=true`, the consumer fetches the version content and sets it as the exchange body. Otherwise, the version metadata object is set as the body. diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java index 99f3091e81a80..e69967e66814d 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java @@ -74,11 +74,6 @@ public class ApicurioRegistryConfiguration implements Cloneable { defaultValue = "true") private boolean failOnValidation = true; - @UriParam(label = "producer", - description = "Schema cache TTL in milliseconds for the validate operation. 0 means no caching.", - defaultValue = "300000") - private long cacheTtl = 300000; - public String getRegistryUrl() { return registryUrl; } @@ -183,14 +178,6 @@ public void setFailOnValidation(boolean failOnValidation) { this.failOnValidation = failOnValidation; } - public long getCacheTtl() { - return cacheTtl; - } - - public void setCacheTtl(long cacheTtl) { - this.cacheTtl = cacheTtl; - } - public ApicurioRegistryConfiguration copy() { try { return (ApicurioRegistryConfiguration) super.clone(); diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java index f190390be696f..00e669dbdf5f0 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java @@ -48,9 +48,6 @@ public interface ApicurioRegistryConstants { @Metadata(description = "The content type of the artifact", javaType = "String") String HEADER_CONTENT_TYPE = "CamelApicurioRegistryContentType"; - @Metadata(description = "Whether the operation is a dry run", javaType = "Boolean") - String HEADER_DRY_RUN = "CamelApicurioRegistryDryRun"; - @Metadata(label = "consumer", description = "The version global ID", javaType = "Long") String HEADER_GLOBAL_ID = "CamelApicurioRegistryGlobalId"; diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java index 07842bfd796ff..9e49b9273054c 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java @@ -17,6 +17,8 @@ package org.apache.camel.component.apicurioregistry; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import io.apicurio.registry.rest.client.RegistryClient; @@ -58,7 +60,9 @@ protected int poll() throws Exception { return 0; } - List versions = results.getVersions(); + List versions = new ArrayList<>(results.getVersions()); + versions.sort(Comparator.comparingLong(SearchedVersion::getGlobalId)); + int count = 0; for (SearchedVersion version : versions) { Long globalId = version.getGlobalId(); @@ -78,19 +82,17 @@ protected int poll() throws Exception { } if (configuration.isFetchContent()) { - InputStream content = client.groups().byGroupId(groupId).artifacts() + try (InputStream content = client.groups().byGroupId(groupId).artifacts() .byArtifactId(artifactId).versions() - .byVersionExpression(version.getVersion()).content().get(); - message.setBody(content); + .byVersionExpression(version.getVersion()).content().get()) { + message.setBody(content.readAllBytes()); + } } else { message.setBody(version); } getProcessor().process(exchange); - - if (lastSeenGlobalId == null || globalId > lastSeenGlobalId) { - lastSeenGlobalId = globalId; - } + lastSeenGlobalId = globalId; count++; } } diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java index 13b5face56ed1..8dcb56d33c52b 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java @@ -18,6 +18,7 @@ import java.io.InputStream; +import com.microsoft.kiota.ApiException; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactMetaData; import io.apicurio.registry.rest.client.models.CreateArtifact; @@ -126,15 +127,16 @@ public void deleteArtifact(Message message) { } @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_GET_ARTIFACT_CONTENT) - public void getArtifactContent(Message message) { + public void getArtifactContent(Message message) throws Exception { String groupId = resolveGroupId(message); String artifactId = resolveArtifactId(message); String version = message.getHeader( ApicurioRegistryConstants.HEADER_VERSION, "branch=latest", String.class); - InputStream content = getClient().groups().byGroupId(groupId).artifacts() - .byArtifactId(artifactId).versions().byVersionExpression(version).content().get(); - message.setBody(content); + try (InputStream content = getClient().groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().byVersionExpression(version).content().get()) { + message.setBody(content.readAllBytes()); + } } @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_GET_ARTIFACT_METADATA) @@ -192,32 +194,26 @@ public void createGroup(Message message) { } @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_TEST_COMPATIBILITY) - public void testCompatibility(Message message) { - String groupId = resolveGroupId(message); - String artifactId = resolveArtifactId(message); - String content = message.getBody(String.class); - String contentType = message.getHeader( - ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); - - CreateVersion createVersion = new CreateVersion(); - VersionContent vc = new VersionContent(); - vc.setContent(content); - vc.setContentType(contentType); - createVersion.setContent(vc); - - try { - getClient().groups().byGroupId(groupId).artifacts() - .byArtifactId(artifactId).versions() - .post(createVersion, config -> config.queryParameters.dryRun = true); - message.setBody(true); - } catch (Exception e) { - message.setBody(false); - message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, e.getMessage()); - } + public void testCompatibility(Message message) throws Exception { + boolean compatible = doDryRun(message); + message.setBody(compatible); } @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_VALIDATE) public void validate(Message message) throws Exception { + boolean valid = doDryRun(message); + message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, valid); + if (!valid && configuration.isFailOnValidation()) { + String errors = message.getHeader( + ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, String.class); + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + throw new ApicurioRegistryValidationException( + "Validation failed for artifact " + groupId + "/" + artifactId, errors); + } + } + + private boolean doDryRun(Message message) throws Exception { String groupId = resolveGroupId(message); String artifactId = resolveArtifactId(message); String content = message.getBody(String.class); @@ -234,14 +230,10 @@ public void validate(Message message) throws Exception { getClient().groups().byGroupId(groupId).artifacts() .byArtifactId(artifactId).versions() .post(createVersion, config -> config.queryParameters.dryRun = true); - message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, true); - } catch (Exception e) { - message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, false); + return true; + } catch (ApiException e) { message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, e.getMessage()); - if (configuration.isFailOnValidation()) { - throw new ApicurioRegistryValidationException( - "Validation failed for artifact " + groupId + "/" + artifactId, e.getMessage()); - } + return false; } } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java index 0976f3abbea35..666353064cad4 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -29,6 +29,7 @@ import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -88,6 +89,46 @@ void testPollNewVersions() throws Exception { MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); } + @Test + void testPollOutOfOrderGlobalIds() throws Exception { + injectMockClient(); + + SearchedVersion v1 = new SearchedVersion(); + v1.setGlobalId(5L); + v1.setVersion("1.0"); + v1.setContentId(100L); + v1.setArtifactType("JSON"); + + SearchedVersion v2 = new SearchedVersion(); + v2.setGlobalId(2L); + v2.setVersion("0.1"); + v2.setContentId(99L); + v2.setArtifactType("JSON"); + + SearchedVersion v3 = new SearchedVersion(); + v3.setGlobalId(8L); + v3.setVersion("2.0"); + v3.setContentId(102L); + v3.setArtifactType("JSON"); + + VersionSearchResults results = new VersionSearchResults(); + results.setVersions(List.of(v1, v2, v3)); + + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().get()) + .thenReturn(results); + + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMinimumMessageCount(3); + MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); + + // Verify exchanges arrive sorted by globalId (ascending) + List receivedIds = mock.getReceivedExchanges().stream() + .map(e -> e.getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class)) + .toList(); + assertEquals(List.of(2L, 5L, 8L), receivedIds); + } + @Test void testPollNoNewVersionsAfterInitial() throws Exception { injectMockClient(); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java index 4c76854d4323b..2263ab93168eb 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java @@ -20,8 +20,10 @@ import java.nio.charset.StandardCharsets; import java.util.List; +import com.microsoft.kiota.ApiException; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactMetaData; +import io.apicurio.registry.rest.client.models.ArtifactSearchResults; import io.apicurio.registry.rest.client.models.CreateArtifact; import io.apicurio.registry.rest.client.models.CreateArtifactResponse; import io.apicurio.registry.rest.client.models.CreateGroup; @@ -30,13 +32,17 @@ import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; import org.apache.camel.BindToRegistry; +import org.apache.camel.CamelExecutionException; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; @@ -83,6 +89,15 @@ public void configure() { from("direct:searchArtifacts") .to("apicurio-registry:testGroup?registryUrl=http://localhost:8080/apis/registry/v3&operation=searchArtifacts"); + from("direct:testCompatibility") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=testCompatibility"); + + from("direct:validate") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=validate&failOnValidation=false"); + + from("direct:validateFail") + .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=validate&failOnValidation=true"); + from("direct:operationFromHeader") .to("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3"); } @@ -206,4 +221,93 @@ void testOperationFromHeader() throws Exception { ApicurioRegistryConstants.HEADER_OPERATION, "getArtifactMetadata"); assertEquals(mockMetadata, result); } + + @Test + void testSearchArtifacts() throws Exception { + String endpointUri + = "apicurio-registry:testGroup?registryUrl=http://localhost:8080/apis/registry/v3&operation=searchArtifacts"; + injectMockClient(endpointUri); + + ArtifactSearchResults mockResults = new ArtifactSearchResults(); + when(mockClient.search().artifacts().get(any())).thenReturn(mockResults); + + Object result = template.requestBody("direct:searchArtifacts", (Object) null); + assertEquals(mockResults, result); + } + + @Test + void testTestCompatibilitySuccess() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=testCompatibility"; + injectMockClient(endpointUri); + + VersionMetaData mockVersion = new VersionMetaData(); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().post(any(CreateVersion.class), any())) + .thenReturn(mockVersion); + + Object result = template.requestBody("direct:testCompatibility", "{\"test\":true}"); + assertEquals(true, result); + } + + @Test + void testTestCompatibilityFailure() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=testCompatibility"; + injectMockClient(endpointUri); + + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().post(any(CreateVersion.class), any())) + .thenThrow(new ApiException("incompatible")); + + Object result = template.requestBody("direct:testCompatibility", "{\"bad\":true}"); + assertEquals(false, result); + } + + @Test + void testValidateSuccess() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=validate&failOnValidation=false"; + injectMockClient(endpointUri); + + VersionMetaData mockVersion = new VersionMetaData(); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().post(any(CreateVersion.class), any())) + .thenReturn(mockVersion); + + var exchange = template.request("direct:validate", ex -> ex.getIn().setBody("{\"test\":true}")); + assertTrue(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, Boolean.class)); + } + + @Test + void testValidateFailureNoThrow() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=validate&failOnValidation=false"; + injectMockClient(endpointUri); + + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().post(any(CreateVersion.class), any())) + .thenThrow(new ApiException("validation error")); + + var exchange = template.request("direct:validate", ex -> ex.getIn().setBody("{\"bad\":true}")); + assertFalse(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, Boolean.class)); + assertNotNull(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)); + } + + @Test + void testValidateFailureThrows() throws Exception { + String endpointUri + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=validate&failOnValidation=true"; + injectMockClient(endpointUri); + + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().post(any(CreateVersion.class), any())) + .thenThrow(new ApiException("validation error")); + + try { + template.requestBody("direct:validateFail", "{\"bad\":true}"); + } catch (CamelExecutionException e) { + assertInstanceOf(ApicurioRegistryValidationException.class, e.getCause()); + } + } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java index 2eeb421dc4b59..ceadda364943f 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java @@ -27,6 +27,8 @@ import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + class ApicurioRegistryConsumerIT extends ApicurioRegistryTestSupport { private static final String JSON_SCHEMA = """ @@ -89,7 +91,4 @@ void testConsumerReceivesNewVersions() throws Exception { assertEquals(artifactId, receivedHeaders.get(ApicurioRegistryConstants.HEADER_ARTIFACT_ID)); } - private void assertEquals(Object expected, Object actual) { - org.junit.jupiter.api.Assertions.assertEquals(expected, actual); - } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java index c615eb8321831..d8eeda7414227 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; class ApicurioRegistryValidateIT extends ApicurioRegistryTestSupport { @@ -108,7 +109,7 @@ void testValidateCompatibleContent() { exchange.getIn().setBody(compatibleSchema); }); - assertNotNull(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT)); + assertEquals(true, result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT)); } @Test @@ -127,6 +128,6 @@ void testTestCompatibility() { exchange.getIn().setBody(compatibleSchema); }); - assertNotNull(result.getIn().getBody()); + assertEquals(true, result.getIn().getBody()); } } diff --git a/parent/pom.xml b/parent/pom.xml index b5b3020552078..628940f6118de 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -79,6 +79,7 @@ 2.0.5 2.0.0.AM27 1.22.0 + 3.3.0 7.28.0 1.13.0 9.11.1 From 459b25b1367105f977213a7088a1a1ccd07453f8 Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Wed, 19 Aug 2026 13:41:15 +0200 Subject: [PATCH 04/13] Fix consumer unit tests and producer IT after review feedback Assisted by Claude, model: claude-opus-4-6 --- .../ApicurioRegistryConsumerTest.java | 75 ++++++++++--------- .../ApicurioRegistryProducerIT.java | 10 +-- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java index 666353064cad4..fc8c429f718e6 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -22,49 +22,61 @@ import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.SearchedVersion; import io.apicurio.registry.rest.client.models.VersionSearchResults; -import org.apache.camel.BindToRegistry; -import org.apache.camel.RoutesBuilder; +import org.apache.camel.CamelContext; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; -import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class ApicurioRegistryConsumerTest extends CamelTestSupport { +class ApicurioRegistryConsumerTest { + + private static final String ENDPOINT_URI + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=500"; private final RegistryClient mockClient = mock(RegistryClient.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); + private CamelContext context; - @BindToRegistry("apicurio-registry") - public ApicurioRegistryComponent getComponent() { + @BeforeEach + void setUp() { + context = new DefaultCamelContext(); ApicurioRegistryComponent component = new ApicurioRegistryComponent(context); component.getConfiguration().setRegistryUrl("http://localhost:8080/apis/registry/v3"); - return component; + context.addComponent("apicurio-registry", component); + } + + @AfterEach + void tearDown() throws Exception { + if (context != null) { + context.stop(); + } } - @Override - protected RoutesBuilder createRouteBuilder() { - return new RouteBuilder() { + private void setupAndStartRoute(VersionSearchResults results) throws Exception { + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().get()) + .thenReturn(results); + + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint(ENDPOINT_URI); + endpoint.setRegistryClient(mockClient); + + context.addRoutes(new RouteBuilder() { @Override public void configure() { - from("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=500") - .to("mock:result"); + from(ENDPOINT_URI).to("mock:result"); } - }; - } + }); - private void injectMockClient() { - ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( - "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=500"); - endpoint.setRegistryClient(mockClient); + context.start(); } @Test void testPollNewVersions() throws Exception { - injectMockClient(); - SearchedVersion v1 = new SearchedVersion(); v1.setGlobalId(1L); v1.setVersion("1.0"); @@ -80,19 +92,15 @@ void testPollNewVersions() throws Exception { VersionSearchResults results = new VersionSearchResults(); results.setVersions(List.of(v1, v2)); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().get()) - .thenReturn(results); + setupAndStartRoute(results); - MockEndpoint mock = getMockEndpoint("mock:result"); + MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class); mock.expectedMinimumMessageCount(2); MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); } @Test void testPollOutOfOrderGlobalIds() throws Exception { - injectMockClient(); - SearchedVersion v1 = new SearchedVersion(); v1.setGlobalId(5L); v1.setVersion("1.0"); @@ -114,15 +122,12 @@ void testPollOutOfOrderGlobalIds() throws Exception { VersionSearchResults results = new VersionSearchResults(); results.setVersions(List.of(v1, v2, v3)); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().get()) - .thenReturn(results); + setupAndStartRoute(results); - MockEndpoint mock = getMockEndpoint("mock:result"); + MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class); mock.expectedMinimumMessageCount(3); MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); - // Verify exchanges arrive sorted by globalId (ascending) List receivedIds = mock.getReceivedExchanges().stream() .map(e -> e.getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class)) .toList(); @@ -131,8 +136,6 @@ void testPollOutOfOrderGlobalIds() throws Exception { @Test void testPollNoNewVersionsAfterInitial() throws Exception { - injectMockClient(); - SearchedVersion v1 = new SearchedVersion(); v1.setGlobalId(1L); v1.setVersion("1.0"); @@ -142,11 +145,9 @@ void testPollNoNewVersionsAfterInitial() throws Exception { VersionSearchResults results = new VersionSearchResults(); results.setVersions(List.of(v1)); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().get()) - .thenReturn(results); + setupAndStartRoute(results); - MockEndpoint mock = getMockEndpoint("mock:result"); + MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class); mock.expectedMessageCount(1); MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java index 1cab39009d65e..97681befadc96 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java @@ -16,7 +16,6 @@ */ package org.apache.camel.component.apicurioregistry.integration; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -124,14 +123,9 @@ void testFullArtifactLifecycle() throws Exception { Exchange contentResult = template.request("direct:operation", exchange -> { exchange.getIn().setHeaders(getContentHeaders); }); - Object contentBody = contentResult.getIn().getBody(); + byte[] contentBody = contentResult.getIn().getBody(byte[].class); assertNotNull(contentBody); - String contentStr; - if (contentBody instanceof InputStream) { - contentStr = new String(((InputStream) contentBody).readAllBytes(), StandardCharsets.UTF_8); - } else { - contentStr = contentBody.toString(); - } + String contentStr = new String(contentBody, StandardCharsets.UTF_8); assertTrue(contentStr.contains("name")); // 5. Update artifact (new version) From faee6e3abc6e68582a53f207b770642d21c19b22 Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Tue, 15 Sep 2026 11:59:17 +0200 Subject: [PATCH 05/13] CAMEL-24172: Address Apicurio Registry review feedback Update to Camel 4.23 and regenerate catalog, BOM, documentation and DSL files. Manage SDK resources, preserve injected clients, distinguish rule violations from infrastructure failures, and expand authentication, validation and consumer coverage. Co-authored-by: GitHub Copilot --- bom/camel-bom/pom.xml | 5 + .../camel/catalog/components.properties | 1 + .../catalog/components/apicurio-registry.json | 99 + .../org/apache/camel/catalog/docs.properties | 1 + .../docs/apicurio-registry-component.adoc | 222 +++ components/camel-apicurio-registry/pom.xml | 8 +- .../ApicurioRegistryEndpointUriFactory.java | 4 +- .../apicurioregistry/apicurio-registry.json | 12 +- .../org/apache/camel/component.properties | 2 +- .../docs/apicurio-registry-component.adoc | 30 +- .../ApicurioRegistryConfiguration.java | 8 +- .../ApicurioRegistryEndpoint.java | 30 +- .../ApicurioRegistryProducer.java | 8 +- .../ApicurioRegistryComponentTest.java | 54 +- .../ApicurioRegistryConsumerTest.java | 69 +- .../ApicurioRegistryEndpointTest.java | 160 ++ .../ApicurioRegistryProducerTest.java | 144 +- .../ApicurioRegistryConsumerIT.java | 32 +- .../ApicurioRegistryProducerIT.java | 54 +- .../ApicurioRegistryValidateIT.java | 59 +- .../apache/camel/main/components.properties | 1 + .../ROOT/examples/json/apicurio-registry.json | 1 + docs/components/modules/ROOT/nav.adoc | 1 + .../pages/apicurio-registry-component.adoc | 1 + .../component/ComponentsBuilderFactory.java | 13 + ...icurioRegistryComponentBuilderFactory.java | 392 ++++ .../endpoint/EndpointBuilderFactory.java | 1 + .../builder/endpoint/EndpointBuilders.java | 1 + .../endpoint/EndpointHeaderBuilders.java | 13 + .../endpoint/StaticEndpointBuilders.java | 46 + ...picurioRegistryEndpointBuilderFactory.java | 1588 +++++++++++++++++ ...el-component-known-dependencies.properties | 1 + parent/pom.xml | 7 + .../pom.xml | 2 +- ...rioRegistryLocalContainerInfraService.java | 1 + 35 files changed, 2952 insertions(+), 119 deletions(-) create mode 100644 catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json create mode 100644 catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java create mode 120000 docs/components/modules/ROOT/examples/json/apicurio-registry.json create mode 120000 docs/components/modules/ROOT/pages/apicurio-registry-component.adoc create mode 100644 dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java create mode 100644 dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index f7228e1f749ae..f9f72f8e8d2d6 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -136,6 +136,11 @@ camel-api 4.24.0-SNAPSHOT + + org.apache.camel + camel-apicurio-registry + 4.23.0-SNAPSHOT + org.apache.camel camel-arangodb 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 a72d3a6c6980d..c4510b3bcbb27 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 @@ -12,6 +12,7 @@ alibaba-ots alibaba-sls alibaba-sms amqp +apicurio-registry arangodb as2 asterisk diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json new file mode 100644 index 0000000000000..5e65924a46600 --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json @@ -0,0 +1,99 @@ +{ + "component": { + "kind": "component", + "name": "apicurio-registry", + "title": "Apicurio Registry", + "description": "Manage artifacts, versions, and groups in Apicurio Registry v3.", + "deprecated": false, + "firstVersion": "4.23.0", + "label": "cloud,api", + "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent", + "supportLevel": "Preview", + "groupId": "org.apache.camel", + "artifactId": "camel-apicurio-registry", + "version": "4.23.0-SNAPSHOT", + "scheme": "apicurio-registry", + "extendsScheme": "", + "syntax": "apicurio-registry:groupId\/artifactId", + "async": false, + "api": false, + "consumerOnly": false, + "producerOnly": false, + "lenientProperties": false, + "browsable": false, + "remote": true + }, + "componentProperties": { + "registryUrl": { "index": 0, "kind": "property", "displayName": "Registry Url", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The Apicurio Registry base URL" }, + "bridgeErrorHandler": { "index": 1, "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." }, + "fetchContent": { "index": 2, "kind": "property", "displayName": "Fetch Content", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to fetch the content for each new version found by the consumer" }, + "artifactType": { "index": 3, "kind": "property", "displayName": "Artifact Type", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "JSON", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The default artifact type" }, + "failOnValidation": { "index": 4, "kind": "property", "displayName": "Fail On Validation", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead." }, + "ifExists": { "index": 5, "kind": "property", "displayName": "If Exists", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "FAIL", "CREATE_VERSION", "FIND_OR_CREATE_VERSION" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "FAIL", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Behavior when artifact already exists" }, + "lazyStartProducer": { "index": 6, "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." }, + "operation": { "index": 7, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, + "autowiredEnabled": { "index": 8, "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." }, + "configuration": { "index": 9, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, + "authType": { "index": 10, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 11, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 12, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 13, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 14, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "tokenEndpoint": { "index": 15, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 16, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + }, + "headers": { + "CamelApicurioRegistryOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_OPERATION" }, + "CamelApicurioRegistryGroupId": { "index": 1, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact group ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_GROUP_ID" }, + "CamelApicurioRegistryArtifactId": { "index": 2, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_ID" }, + "CamelApicurioRegistryArtifactType": { "index": 3, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact type (e.g. AVRO, PROTOBUF, JSON, OPENAPI)", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_TYPE" }, + "CamelApicurioRegistryVersion": { "index": 4, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact version expression", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION" }, + "CamelApicurioRegistryArtifactName": { "index": 5, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact name", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_NAME" }, + "CamelApicurioRegistryArtifactDescription": { "index": 6, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The artifact description", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_ARTIFACT_DESCRIPTION" }, + "CamelApicurioRegistryIfExists": { "index": 7, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Behavior when artifact already exists (FAIL, CREATE_VERSION, FIND_OR_CREATE_VERSION)", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_IF_EXISTS" }, + "CamelApicurioRegistryContentType": { "index": 8, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content type of the artifact", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_TYPE" }, + "CamelApicurioRegistryGlobalId": { "index": 9, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version global ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_GLOBAL_ID" }, + "CamelApicurioRegistryContentId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, + "CamelApicurioRegistryVersionState": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, + "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether validation passed", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, + "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Validation error details", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } + }, + "properties": { + "groupId": { "index": 0, "kind": "path", "displayName": "Group Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact group ID" }, + "artifactId": { "index": 1, "kind": "path", "displayName": "Artifact Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact ID" }, + "registryUrl": { "index": 2, "kind": "parameter", "displayName": "Registry Url", "group": "common", "label": "common", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The Apicurio Registry base URL" }, + "fetchContent": { "index": 3, "kind": "parameter", "displayName": "Fetch Content", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to fetch the content for each new version found by the consumer" }, + "sendEmptyMessageWhenIdle": { "index": 4, "kind": "parameter", "displayName": "Send Empty Message When Idle", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If the polling consumer did not poll any files, you can enable this option to send an empty message (no body) instead." }, + "bridgeErrorHandler": { "index": 5, "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." }, + "exceptionHandler": { "index": 6, "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": 7, "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." }, + "pollStrategy": { "index": 8, "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." }, + "artifactType": { "index": 9, "kind": "parameter", "displayName": "Artifact Type", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "JSON", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The default artifact type" }, + "failOnValidation": { "index": 10, "kind": "parameter", "displayName": "Fail On Validation", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Whether to throw an exception on validation failure (validate operation). When false, sets result headers instead." }, + "ifExists": { "index": 11, "kind": "parameter", "displayName": "If Exists", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "FAIL", "CREATE_VERSION", "FIND_OR_CREATE_VERSION" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "FAIL", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Behavior when artifact already exists" }, + "operation": { "index": 12, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, + "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." }, + "registryClient": { "index": 14, "kind": "parameter", "displayName": "Registry Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.apicurio.registry.rest.client.RegistryClient", "deprecated": false, "autowired": false, "secret": false, "description": "To use a pre-configured RegistryClient instance" }, + "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." }, + "authType": { "index": 29, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 30, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 31, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 32, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 33, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "tokenEndpoint": { "index": 34, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 35, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + } +} 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 f952957e6e25d..f14dbffb0b0ab 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 @@ -18,6 +18,7 @@ alibaba-ots-component alibaba-sls-component alibaba-sms-component amqp-component +apicurio-registry-component arangodb-component as2-component asn1-dataformat diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc new file mode 100644 index 0000000000000..7ac8c239c242d --- /dev/null +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc @@ -0,0 +1,222 @@ += Apicurio Registry Component +:doctitle: Apicurio Registry +:shortname: apicurio-registry +:artifactid: camel-apicurio-registry +:description: Manage artifacts, versions, and groups in Apicurio Registry v3. +:since: 4.23 +:supportlevel: Preview +:tabs-sync-option: +:component-header: Both producer and consumer are supported + +*Since Camel {since}* + +*{component-header}* + +The Apicurio Registry component provides integration with https://www.apicur.io/registry/[Apicurio Registry] v3 +using the https://github.com/Apicurio/apicurio-registry[Apicurio Registry Java SDK]. + +It supports CRUD operations on artifacts, groups, and versions, as well as schema validation +and polling for new artifact versions. + +Maven users will need to add the following dependency to their `pom.xml` +for this component: + +[source,xml] +---- + + org.apache.camel + camel-apicurio-registry + x.x.x + + +---- + +== URI format + +---- +apicurio-registry:groupId/artifactId[?options] +---- + +Where `groupId` and `artifactId` identify the registry artifact. Both are optional for +operations that do not require them (e.g., `searchArtifacts`, `createGroup`). + +The first `/` separates the group ID from the artifact ID. Group IDs containing `/` +cannot be represented in the URI path. Producers can instead supply the group ID using +the `CamelApicurioRegistryGroupId` header, which overrides the URI value. + +// component options: START +include::partial$component-configure-options.adoc[] +include::partial$component-endpoint-options.adoc[] +include::partial$component-endpoint-headers.adoc[] +// component options: END + +== Producer Operations + +The producer supports the following operations, set via the `operation` endpoint option or +the `CamelApicurioRegistryOperation` header: + +[cols="1,3"] +|=== +| Operation | Description + +| `createArtifact` | Create a new artifact. Body contains the artifact content. +| `updateArtifact` | Create a new version of an existing artifact. Body contains the new content. +| `deleteArtifact` | Delete an artifact. +| `getArtifactContent` | Retrieve the content of an artifact version. +| `getArtifactMetadata` | Retrieve artifact metadata. +| `searchArtifacts` | Search for artifacts using filter headers. +| `listVersions` | List all versions of an artifact. +| `createGroup` | Create a new group. +| `testCompatibility` | Test schema compatibility (dry run). Returns `true`/`false` in the body. +| `validate` | Validate the message body against the artifact's rules (dry run). Throws `ApicurioRegistryValidationException` on failure when `failOnValidation=true`. +|=== + +Artifact content supplied to `createArtifact`, `updateArtifact`, `validate`, and +`testCompatibility` is converted to a string for the SDK's `VersionContent` model. +For AVRO and PROTOBUF, supply the schema definition, rather than serialized message data. +`getArtifactContent` returns a `byte[]`; the component closes the SDK response stream +before passing the content to the route. + +`validate` and `testCompatibility` check a proposed artifact version against the registry's +configured rules without storing it. They do not validate application messages against a +stored schema. Authentication, connectivity, and other non-rule failures propagate as +exceptions, rather than returning a negative validation result. + +== Consumer + +The consumer polls the registry for new versions of the specified artifact. It tracks the last +seen `globalId` and only delivers new versions. Set the `delay` option to control the polling +interval (default: 500ms). + +On first start, all existing versions of the artifact are emitted as exchanges. Subsequent polls +only deliver versions created after the consumer started. The watermark is held in memory and +resets on route/context restart. + +When `fetchContent=true`, the consumer fetches the version content and sets it as a `byte[]` exchange body, +closing the SDK response stream before processing the exchange. +Otherwise, the version metadata object is set as the body. + +== Examples + +=== Create an artifact + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:create") + .setBody(constant("{\"type\":\"record\",\"name\":\"Test\",\"fields\":[]}")) + .to("apicurio-registry:myGroup/myArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=createArtifact&artifactType=AVRO"); +---- + +XML:: ++ +[source,xml] +---- + + + {"type":"record","name":"Test","fields":[]} + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:create + steps: + - setBody: + constant: '{"type":"record","name":"Test","fields":[]}' + - to: + uri: apicurio-registry:myGroup/myArtifact + parameters: + registryUrl: http://localhost:8080/apis/registry/v3 + operation: createArtifact + artifactType: AVRO +---- +==== + +=== Poll for new versions + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("apicurio-registry:myGroup/myArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=5000") + .log("New version detected: ${header.CamelApicurioRegistryVersion}") + .to("log:newVersions"); +---- + +XML:: ++ +[source,xml] +---- + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: apicurio-registry:myGroup/myArtifact + parameters: + registryUrl: http://localhost:8080/apis/registry/v3 + delay: 5000 + steps: + - log: + message: "New version detected: ${header.CamelApicurioRegistryVersion}" + - to: + uri: log:newVersions +---- +==== + +=== Validate a proposed schema version + +[source,java] +---- +from("direct:validateSchema") + .doTry() + .to("apicurio-registry:my-group/order-schema?operation=validate®istryUrl=http://localhost:8080/apis/registry/v3") + .to("direct:publishSchema") + .doCatch(ApicurioRegistryValidationException.class) + .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400)) + .setBody(simple("${exception.validationErrors}")) + .end(); +---- + +== Authentication + +The endpoint manages the Vert.x instance used by its SDK client and closes it when +the endpoint stops. A client supplied through `registryClient` remains caller-owned; +the caller is responsible for its underlying HTTP resources. + +The component supports three authentication modes via the `authType` option: + +* `none` (default): No authentication. +* `basic`: Basic authentication using `username` and `password`. +* `oidc`: OAuth2/OIDC using `tokenEndpoint`, `clientId`, `clientSecret`, and optionally `scope`. + +Example with OIDC: + +[source,java] +---- +from("direct:secured") + .to("apicurio-registry:myGroup/myArtifact?registryUrl=http://localhost:8080/apis/registry/v3" + + "&operation=getArtifactContent" + + "&authType=oidc" + + "&tokenEndpoint=http://keycloak:8080/realms/registry/protocol/openid-connect/token" + + "&clientId=registry-client&clientSecret=secret"); +---- diff --git a/components/camel-apicurio-registry/pom.xml b/components/camel-apicurio-registry/pom.xml index 616536bfd200d..aaa2126aa7bb8 100644 --- a/components/camel-apicurio-registry/pom.xml +++ b/components/camel-apicurio-registry/pom.xml @@ -24,7 +24,7 @@ components org.apache.camel - 4.22.0-SNAPSHOT + 4.23.0-SNAPSHOT camel-apicurio-registry @@ -44,10 +44,14 @@ io.apicurio apicurio-registry-java-sdk - ${apicurio-registry-sdk-version} + + org.assertj + assertj-core + test + org.apache.camel camel-test-junit5 diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java index e2db8a74a3fca..41176af63ac2a 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java @@ -62,11 +62,9 @@ public class ApicurioRegistryEndpointUriFactory extends org.apache.camel.support props.add("useFixedDelay"); props.add("username"); PROPERTY_NAMES = Collections.unmodifiableSet(props); - Set secretProps = new HashSet<>(4); - secretProps.add("clientId"); + Set secretProps = new HashSet<>(2); secretProps.add("clientSecret"); secretProps.add("password"); - secretProps.add("username"); SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps); ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet(); Map prefixes = new HashMap<>(1); diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json index 13430aac6a642..5e65924a46600 100644 --- a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json @@ -5,13 +5,13 @@ "title": "Apicurio Registry", "description": "Manage artifacts, versions, and groups in Apicurio Registry v3.", "deprecated": false, - "firstVersion": "4.22.0", + "firstVersion": "4.23.0", "label": "cloud,api", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent", "supportLevel": "Preview", "groupId": "org.apache.camel", "artifactId": "camel-apicurio-registry", - "version": "4.22.0-SNAPSHOT", + "version": "4.23.0-SNAPSHOT", "scheme": "apicurio-registry", "extendsScheme": "", "syntax": "apicurio-registry:groupId\/artifactId", @@ -35,12 +35,12 @@ "autowiredEnabled": { "index": 8, "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." }, "configuration": { "index": 9, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, "authType": { "index": 10, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 11, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientId": { "index": 11, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, "clientSecret": { "index": 12, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, "password": { "index": 13, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, "scope": { "index": 14, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, "tokenEndpoint": { "index": 15, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 16, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "username": { "index": 16, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } }, "headers": { "CamelApicurioRegistryOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_OPERATION" }, @@ -89,11 +89,11 @@ "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." }, "authType": { "index": 29, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 30, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientId": { "index": 30, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, "clientSecret": { "index": 31, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, "password": { "index": 32, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, "scope": { "index": 33, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, "tokenEndpoint": { "index": 34, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 35, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "username": { "index": 35, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } } } diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties index d66643d9caeb5..7036bd840740a 100644 --- a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties @@ -2,6 +2,6 @@ components=apicurio-registry groupId=org.apache.camel artifactId=camel-apicurio-registry -version=4.22.0-SNAPSHOT +version=4.23.0-SNAPSHOT projectName=Camel :: Apicurio Registry projectDescription=Camel Apicurio Registry component diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc index 2759669156c04..7ac8c239c242d 100644 --- a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -3,7 +3,7 @@ :shortname: apicurio-registry :artifactid: camel-apicurio-registry :description: Manage artifacts, versions, and groups in Apicurio Registry v3. -:since: 4.22 +:since: 4.23 :supportlevel: Preview :tabs-sync-option: :component-header: Both producer and consumer are supported @@ -40,6 +40,10 @@ apicurio-registry:groupId/artifactId[?options] Where `groupId` and `artifactId` identify the registry artifact. Both are optional for operations that do not require them (e.g., `searchArtifacts`, `createGroup`). +The first `/` separates the group ID from the artifact ID. Group IDs containing `/` +cannot be represented in the URI path. Producers can instead supply the group ID using +the `CamelApicurioRegistryGroupId` header, which overrides the URI value. + // component options: START include::partial$component-configure-options.adoc[] include::partial$component-endpoint-options.adoc[] @@ -67,6 +71,17 @@ the `CamelApicurioRegistryOperation` header: | `validate` | Validate the message body against the artifact's rules (dry run). Throws `ApicurioRegistryValidationException` on failure when `failOnValidation=true`. |=== +Artifact content supplied to `createArtifact`, `updateArtifact`, `validate`, and +`testCompatibility` is converted to a string for the SDK's `VersionContent` model. +For AVRO and PROTOBUF, supply the schema definition, rather than serialized message data. +`getArtifactContent` returns a `byte[]`; the component closes the SDK response stream +before passing the content to the route. + +`validate` and `testCompatibility` check a proposed artifact version against the registry's +configured rules without storing it. They do not validate application messages against a +stored schema. Authentication, connectivity, and other non-rule failures propagate as +exceptions, rather than returning a negative validation result. + == Consumer The consumer polls the registry for new versions of the specified artifact. It tracks the last @@ -77,7 +92,8 @@ On first start, all existing versions of the artifact are emitted as exchanges. only deliver versions created after the consumer started. The watermark is held in memory and resets on route/context restart. -When `fetchContent=true`, the consumer fetches the version content and sets it as the exchange body. +When `fetchContent=true`, the consumer fetches the version content and sets it as a `byte[]` exchange body, +closing the SDK response stream before processing the exchange. Otherwise, the version metadata object is set as the body. == Examples @@ -167,14 +183,14 @@ YAML:: ---- ==== -=== Validate message against a schema +=== Validate a proposed schema version [source,java] ---- -from("platform-http:/orders") +from("direct:validateSchema") .doTry() .to("apicurio-registry:my-group/order-schema?operation=validate®istryUrl=http://localhost:8080/apis/registry/v3") - .to("bean:orderService") + .to("direct:publishSchema") .doCatch(ApicurioRegistryValidationException.class) .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400)) .setBody(simple("${exception.validationErrors}")) @@ -183,6 +199,10 @@ from("platform-http:/orders") == Authentication +The endpoint manages the Vert.x instance used by its SDK client and closes it when +the endpoint stops. A client supplied through `registryClient` remains caller-owned; +the caller is responsible for its underlying HTTP resources. + The component supports three authentication modes via the `authType` option: * `none` (default): No authentication. diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java index e69967e66814d..4622368747376 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java @@ -40,19 +40,19 @@ public class ApicurioRegistryConfiguration implements Cloneable { description = "The authentication type to use") private String authType = "none"; - @UriParam(label = "security", security = "secret", description = "Username for basic authentication") + @UriParam(label = "security", description = "Username for basic authentication") private String username; - @UriParam(label = "security", security = "secret", description = "Password for basic authentication") + @UriParam(label = "security", secret = true, description = "Password for basic authentication") private String password; @UriParam(label = "security", description = "OAuth2 token endpoint URL") private String tokenEndpoint; - @UriParam(label = "security", security = "secret", description = "OAuth2 client ID") + @UriParam(label = "security", description = "OAuth2 client ID") private String clientId; - @UriParam(label = "security", security = "secret", description = "OAuth2 client secret") + @UriParam(label = "security", secret = true, description = "OAuth2 client secret") private String clientSecret; @UriParam(label = "security", description = "OAuth2 scope") diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java index 5d6b9dc464a14..49bf8b434cef6 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java @@ -16,9 +16,12 @@ */ package org.apache.camel.component.apicurioregistry; +import java.util.concurrent.TimeUnit; + import io.apicurio.registry.client.RegistryClientFactory; import io.apicurio.registry.client.common.RegistryClientOptions; import io.apicurio.registry.rest.client.RegistryClient; +import io.vertx.core.Vertx; import org.apache.camel.Category; import org.apache.camel.Consumer; import org.apache.camel.Processor; @@ -32,7 +35,7 @@ /** * Manage artifacts, versions, and groups in Apicurio Registry v3. */ -@UriEndpoint(firstVersion = "4.22.0", scheme = "apicurio-registry", title = "Apicurio Registry", +@UriEndpoint(firstVersion = "4.23.0", scheme = "apicurio-registry", title = "Apicurio Registry", syntax = "apicurio-registry:groupId/artifactId", category = { Category.CLOUD, Category.API }, headersClass = ApicurioRegistryConstants.class) public class ApicurioRegistryEndpoint extends ScheduledPollEndpoint implements EndpointServiceLocation { @@ -49,6 +52,8 @@ public class ApicurioRegistryEndpoint extends ScheduledPollEndpoint implements E @UriParam(label = "advanced", description = "To use a pre-configured RegistryClient instance") private RegistryClient registryClient; + private Vertx vertx; + ApicurioRegistryEndpoint(String uri, ApicurioRegistryComponent component, ApicurioRegistryConfiguration configuration, String groupId, String artifactId) { @@ -74,18 +79,35 @@ public Consumer createConsumer(Processor processor) throws Exception { protected void doStart() throws Exception { super.doStart(); if (registryClient == null) { - registryClient = createRegistryClient(); + vertx = Vertx.vertx(); + try { + registryClient = createRegistryClient(); + } catch (Exception e) { + closeVertx(); + throw e; + } } } @Override protected void doStop() throws Exception { super.doStop(); - registryClient = null; + if (vertx != null) { + registryClient = null; + closeVertx(); + } + } + + private void closeVertx() throws Exception { + try { + vertx.close().toCompletionStage().toCompletableFuture().get(30, TimeUnit.SECONDS); + } finally { + vertx = null; + } } private RegistryClient createRegistryClient() { - RegistryClientOptions options = RegistryClientOptions.create(configuration.getRegistryUrl()); + RegistryClientOptions options = RegistryClientOptions.create(configuration.getRegistryUrl(), vertx); String authType = configuration.getAuthType(); if ("basic".equalsIgnoreCase(authType)) { options.basicAuth(configuration.getUsername(), configuration.getPassword()); diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java index 8dcb56d33c52b..3ce2fbfc77f03 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java @@ -18,7 +18,6 @@ import java.io.InputStream; -import com.microsoft.kiota.ApiException; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactMetaData; import io.apicurio.registry.rest.client.models.CreateArtifact; @@ -27,6 +26,7 @@ import io.apicurio.registry.rest.client.models.CreateVersion; import io.apicurio.registry.rest.client.models.GroupMetaData; import io.apicurio.registry.rest.client.models.IfArtifactExists; +import io.apicurio.registry.rest.client.models.RuleViolationProblemDetails; import io.apicurio.registry.rest.client.models.VersionContent; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; @@ -214,6 +214,7 @@ public void validate(Message message) throws Exception { } private boolean doDryRun(Message message) throws Exception { + message.removeHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS); String groupId = resolveGroupId(message); String artifactId = resolveArtifactId(message); String content = message.getBody(String.class); @@ -231,8 +232,9 @@ private boolean doDryRun(Message message) throws Exception { .byArtifactId(artifactId).versions() .post(createVersion, config -> config.queryParameters.dryRun = true); return true; - } catch (ApiException e) { - message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, e.getMessage()); + } catch (RuleViolationProblemDetails e) { + message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, + e.getDetail() != null ? e.getDetail() : e.getTitle()); return false; } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java index 70424783ce1b2..4712390e9f81a 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java @@ -19,9 +19,7 @@ import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.assertj.core.api.Assertions.assertThat; class ApicurioRegistryComponentTest extends CamelTestSupport { @@ -29,19 +27,17 @@ class ApicurioRegistryComponentTest extends CamelTestSupport { void testEndpointCreatedWithGroupAndArtifact() throws Exception { ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( "apicurio-registry:myGroup/myArtifact?registryUrl=http://localhost:8080/apis/registry/v3"); - assertNotNull(endpoint); - assertEquals("myGroup", endpoint.getGroupId()); - assertEquals("myArtifact", endpoint.getArtifactId()); - assertEquals("http://localhost:8080/apis/registry/v3", endpoint.getConfiguration().getRegistryUrl()); + assertThat(endpoint.getGroupId()).isEqualTo("myGroup"); + assertThat(endpoint.getArtifactId()).isEqualTo("myArtifact"); + assertThat(endpoint.getConfiguration().getRegistryUrl()).isEqualTo("http://localhost:8080/apis/registry/v3"); } @Test void testEndpointCreatedWithGroupOnly() throws Exception { ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( "apicurio-registry:myGroup?registryUrl=http://localhost:8080/apis/registry/v3"); - assertNotNull(endpoint); - assertEquals("myGroup", endpoint.getGroupId()); - assertNull(endpoint.getArtifactId()); + assertThat(endpoint.getGroupId()).isEqualTo("myGroup"); + assertThat(endpoint.getArtifactId()).isNull(); } @Test @@ -49,10 +45,9 @@ void testEndpointWithAuthOptions() throws Exception { ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3" + "&authType=basic&username=user&password=pass"); - assertNotNull(endpoint); - assertEquals("basic", endpoint.getConfiguration().getAuthType()); - assertEquals("user", endpoint.getConfiguration().getUsername()); - assertEquals("pass", endpoint.getConfiguration().getPassword()); + assertThat(endpoint.getConfiguration().getAuthType()).isEqualTo("basic"); + assertThat(endpoint.getConfiguration().getUsername()).isEqualTo("user"); + assertThat(endpoint.getConfiguration().getPassword()).isEqualTo("pass"); } @Test @@ -60,8 +55,7 @@ void testEndpointWithOperationOption() throws Exception { ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3" + "&operation=createArtifact"); - assertNotNull(endpoint); - assertEquals("createArtifact", endpoint.getConfiguration().getOperation()); + assertThat(endpoint.getConfiguration().getOperation()).isEqualTo("createArtifact"); } @Test @@ -73,17 +67,33 @@ void testConfigurationCopy() { config.setUsername("user"); ApicurioRegistryConfiguration copy = config.copy(); - assertEquals(config.getRegistryUrl(), copy.getRegistryUrl()); - assertEquals(config.getOperation(), copy.getOperation()); - assertEquals(config.getAuthType(), copy.getAuthType()); - assertEquals(config.getUsername(), copy.getUsername()); + assertThat(copy).isNotSameAs(config); + assertThat(copy.getRegistryUrl()).isEqualTo(config.getRegistryUrl()); + assertThat(copy.getOperation()).isEqualTo(config.getOperation()); + assertThat(copy.getAuthType()).isEqualTo(config.getAuthType()); + assertThat(copy.getUsername()).isEqualTo(config.getUsername()); } @Test void testEndpointServiceLocation() throws Exception { ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3"); - assertEquals("http://localhost:8080/apis/registry/v3", endpoint.getServiceUrl()); - assertEquals("http", endpoint.getServiceProtocol()); + assertThat(endpoint.getServiceUrl()).isEqualTo("http://localhost:8080/apis/registry/v3"); + assertThat(endpoint.getServiceProtocol()).isEqualTo("http"); + } + + @Test + void testEndpointWithOidcOptions() { + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3" + + "&authType=oidc&tokenEndpoint=http://localhost:8080/token" + + "&clientId=registry-client&clientSecret=secret&scope=registry", + ApicurioRegistryEndpoint.class); + ApicurioRegistryConfiguration configuration = endpoint.getConfiguration(); + assertThat(configuration.getAuthType()).isEqualTo("oidc"); + assertThat(configuration.getTokenEndpoint()).isEqualTo("http://localhost:8080/token"); + assertThat(configuration.getClientId()).isEqualTo("registry-client"); + assertThat(configuration.getClientSecret()).isEqualTo("secret"); + assertThat(configuration.getScope()).isEqualTo("registry"); } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java index fc8c429f718e6..be89c3b7b236b 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -16,6 +16,9 @@ */ package org.apache.camel.component.apicurioregistry; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -23,6 +26,7 @@ import io.apicurio.registry.rest.client.models.SearchedVersion; import io.apicurio.registry.rest.client.models.VersionSearchResults; import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.impl.DefaultCamelContext; @@ -30,8 +34,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class ApicurioRegistryConsumerTest { @@ -39,7 +47,7 @@ class ApicurioRegistryConsumerTest { private static final String ENDPOINT_URI = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=500"; - private final RegistryClient mockClient = mock(RegistryClient.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); + private final RegistryClient mockClient = mock(RegistryClient.class, RETURNS_DEEP_STUBS); private CamelContext context; @BeforeEach @@ -131,7 +139,7 @@ void testPollOutOfOrderGlobalIds() throws Exception { List receivedIds = mock.getReceivedExchanges().stream() .map(e -> e.getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class)) .toList(); - assertEquals(List.of(2L, 5L, 8L), receivedIds); + assertThat(receivedIds).containsExactly(2L, 5L, 8L); } @Test @@ -145,10 +153,57 @@ void testPollNoNewVersionsAfterInitial() throws Exception { VersionSearchResults results = new VersionSearchResults(); results.setVersions(List.of(v1)); - setupAndStartRoute(results); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().get()).thenReturn(results); + ApicurioRegistryEndpoint endpoint = context.getEndpoint(ENDPOINT_URI, ApicurioRegistryEndpoint.class); + endpoint.setRegistryClient(mockClient); + List exchanges = new ArrayList<>(); + ApicurioRegistryConsumer consumer = (ApicurioRegistryConsumer) endpoint.createConsumer(exchanges::add); + assertThat(consumer.poll()).isEqualTo(1); + assertThat(consumer.poll()).isZero(); - MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class); - mock.expectedMessageCount(1); - MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); + SearchedVersion v2 = new SearchedVersion(); + v2.setGlobalId(2L); + v2.setVersion("2.0"); + results.setVersions(List.of(v2, v1)); + assertThat(consumer.poll()).isEqualTo(1); + assertThat(consumer.poll()).isZero(); + assertThat(exchanges).hasSize(2); + assertThat(exchanges.get(1).getIn().getBody()).isSameAs(v2); + } + + @Test + void testFetchContentClosesStream() throws Exception { + ApicurioRegistryEndpoint endpoint = context.getEndpoint(ENDPOINT_URI + "&fetchContent=true", + ApicurioRegistryEndpoint.class); + endpoint.setRegistryClient(mockClient); + SearchedVersion version = new SearchedVersion(); + version.setGlobalId(1L); + version.setVersion("1"); + VersionSearchResults results = new VersionSearchResults(); + results.setVersions(List.of(version)); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().get()).thenReturn(results); + byte[] content = "{\"type\":\"object\"}".getBytes(StandardCharsets.UTF_8); + ByteArrayInputStream stream = spy(new ByteArrayInputStream(content)); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("1").content().get()).thenReturn(stream); + List exchanges = new ArrayList<>(); + ApicurioRegistryConsumer consumer = (ApicurioRegistryConsumer) endpoint.createConsumer(exchanges::add); + + assertThat(consumer.poll()).isEqualTo(1); + assertThat(exchanges.get(0).getIn().getBody()).isEqualTo(content); + verify(stream).close(); + } + + @Test + void testMissingArtifactId() throws Exception { + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:testGroup?registryUrl=http://localhost:8080/apis/registry/v3", + ApicurioRegistryEndpoint.class); + ApicurioRegistryConsumer consumer = (ApicurioRegistryConsumer) endpoint.createConsumer(exchange -> { + }); + assertThatThrownBy(consumer::poll).isInstanceOf(IllegalArgumentException.class) + .hasMessage("Both groupId and artifactId are required for the consumer"); } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java new file mode 100644 index 0000000000000..2197a6ba7fd2c --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.apicurioregistry; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicReference; + +import com.sun.net.httpserver.HttpServer; +import io.apicurio.registry.client.RegistryClientFactory; +import io.apicurio.registry.client.common.RegistryClientOptions; +import io.apicurio.registry.rest.client.RegistryClient; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ApicurioRegistryEndpointTest { + + @Test + void testBasicAuthentication() throws Exception { + testAuthentication("basic&username=user&password=pass", "Basic dXNlcjpwYXNz", false); + } + + @Test + void testOidcAuthentication() throws Exception { + testAuthentication("oidc&clientId=registry-client&clientSecret=secret&scope=registry", "Bearer test-token", true); + } + + private void testAuthentication(String options, String authorization, boolean oidc) throws Exception { + AtomicReference registryAuthorization = new AtomicReference<>(); + AtomicReference tokenRequest = new AtomicReference<>(); + AtomicReference tokenAuthorization = new AtomicReference<>(); + HttpServer server = HttpServer.create(new InetSocketAddress("localhost", 0), 0); + server.createContext("/token", request -> { + tokenRequest.set(new String(request.getRequestBody().readAllBytes(), StandardCharsets.UTF_8)); + tokenAuthorization.set(request.getRequestHeaders().getFirst("Authorization")); + byte[] response = "{\"access_token\":\"test-token\",\"token_type\":\"Bearer\",\"expires_in\":300}" + .getBytes(StandardCharsets.UTF_8); + request.getResponseHeaders().set("Content-Type", "application/json"); + request.sendResponseHeaders(200, response.length); + try (var body = request.getResponseBody()) { + body.write(response); + } + }); + server.createContext("/apis/registry/v3/groups/g/artifacts/a", request -> { + registryAuthorization.set(request.getRequestHeaders().getFirst("Authorization")); + byte[] response = "{\"artifactId\":\"a\",\"groupId\":\"g\"}".getBytes(StandardCharsets.UTF_8); + request.getResponseHeaders().set("Content-Type", "application/json"); + request.sendResponseHeaders(200, response.length); + try (var body = request.getResponseBody()) { + body.write(response); + } + }); + server.start(); + try (DefaultCamelContext context = new DefaultCamelContext()) { + String baseUrl = "http://localhost:" + server.getAddress().getPort(); + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:g/a?registryUrl=" + baseUrl + "/apis/registry/v3&authType=" + options + + (oidc ? "&tokenEndpoint=" + baseUrl + "/token" : ""), + ApicurioRegistryEndpoint.class); + endpoint.start(); + try { + assertThat(endpoint.getRegistryClient().groups().byGroupId("g").artifacts().byArtifactId("a") + .get().getArtifactId()).isEqualTo("a"); + assertThat(registryAuthorization.get()).isEqualTo(authorization); + if (oidc) { + assertThat(tokenRequest.get()).contains("grant_type=client_credentials", "scope=registry"); + assertThat(tokenAuthorization.get()).isEqualTo("Basic cmVnaXN0cnktY2xpZW50OnNlY3JldA=="); + } + } finally { + endpoint.stop(); + } + } finally { + server.stop(0); + } + } + + @Test + void testOwnedResourcesClosedOnStopAndRestart() throws Exception { + Vertx vertx = mock(Vertx.class); + when(vertx.close()).thenReturn(Future.succeededFuture()); + RegistryClient client = mock(RegistryClient.class); + try (MockedStatic vertxFactory = mockStatic(Vertx.class); + MockedStatic clientFactory = mockStatic(RegistryClientFactory.class); + DefaultCamelContext context = new DefaultCamelContext()) { + vertxFactory.when(Vertx::vertx).thenReturn(vertx); + clientFactory.when(() -> RegistryClientFactory.create(any())).thenAnswer(call -> { + assertThat(call.getArgument(0, RegistryClientOptions.class).getVertx()) + .isSameAs(vertx); + return client; + }); + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3", ApicurioRegistryEndpoint.class); + endpoint.start(); + assertThat(endpoint.getRegistryClient()).isSameAs(client); + endpoint.stop(); + assertThat(endpoint.getRegistryClient()).isNull(); + endpoint.start(); + endpoint.stop(); + verify(vertx, times(2)).close(); + } + } + + @Test + void testFailedClientCreationClosesResources() throws Exception { + Vertx vertx = mock(Vertx.class); + when(vertx.close()).thenReturn(Future.succeededFuture()); + try (MockedStatic vertxFactory = mockStatic(Vertx.class); + MockedStatic clientFactory = mockStatic(RegistryClientFactory.class); + DefaultCamelContext context = new DefaultCamelContext()) { + vertxFactory.when(Vertx::vertx).thenReturn(vertx); + clientFactory.when(() -> RegistryClientFactory.create(any())) + .thenThrow(new IllegalArgumentException("invalid client")); + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3", ApicurioRegistryEndpoint.class); + assertThatThrownBy(endpoint::start).isInstanceOf(IllegalArgumentException.class).hasMessage("invalid client"); + verify(vertx).close(); + } + } + + @Test + void testCallerOwnedClientSurvivesRestart() throws Exception { + RegistryClient client = mock(RegistryClient.class); + try (DefaultCamelContext context = new DefaultCamelContext()) { + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3", ApicurioRegistryEndpoint.class); + endpoint.setRegistryClient(client); + endpoint.start(); + endpoint.stop(); + endpoint.start(); + assertThat(endpoint.getRegistryClient()).isSameAs(client); + endpoint.stop(); + } + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java index 2263ab93168eb..3beb5c5f9c739 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java @@ -19,9 +19,12 @@ import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import com.microsoft.kiota.ApiException; import io.apicurio.registry.rest.client.RegistryClient; +import io.apicurio.registry.rest.client.groups.item.artifacts.ArtifactsRequestBuilder; import io.apicurio.registry.rest.client.models.ArtifactMetaData; import io.apicurio.registry.rest.client.models.ArtifactSearchResults; import io.apicurio.registry.rest.client.models.CreateArtifact; @@ -29,6 +32,9 @@ import io.apicurio.registry.rest.client.models.CreateGroup; import io.apicurio.registry.rest.client.models.CreateVersion; import io.apicurio.registry.rest.client.models.GroupMetaData; +import io.apicurio.registry.rest.client.models.IfArtifactExists; +import io.apicurio.registry.rest.client.models.ProblemDetails; +import io.apicurio.registry.rest.client.models.RuleViolationProblemDetails; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; import org.apache.camel.BindToRegistry; @@ -37,21 +43,23 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.test.junit5.CamelTestSupport; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class ApicurioRegistryProducerTest extends CamelTestSupport { - private final RegistryClient mockClient = mock(RegistryClient.class, org.mockito.Mockito.RETURNS_DEEP_STUBS); + private final RegistryClient mockClient = mock(RegistryClient.class, RETURNS_DEEP_STUBS); @BindToRegistry("apicurio-registry") public ApicurioRegistryComponent getComponent() { @@ -120,10 +128,35 @@ void testCreateArtifact() throws Exception { .thenReturn(mockResponse); Object result = template.requestBody("direct:createArtifact", "{\"test\":true}"); - assertNotNull(result); + assertThat(result).isSameAs(mockResponse); verify(mockClient.groups().byGroupId("testGroup").artifacts()).post(any(CreateArtifact.class), any()); } + @Test + void testCreateHeaderOverrides() throws Exception { + injectMockClient("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3" + + "&operation=createArtifact"); + when(mockClient.groups().byGroupId("other-group").artifacts().post(any(CreateArtifact.class), any())) + .thenAnswer(call -> { + CreateArtifact request = call.getArgument(0); + assertThat(request.getArtifactId()).isEqualTo("other-artifact"); + assertThat(request.getArtifactType()).isEqualTo("AVRO"); + assertThat(request.getFirstVersion().getContent().getContent()).isEqualTo("{\"type\":\"string\"}"); + ArtifactsRequestBuilder builder = mockClient.groups().byGroupId("other-group").artifacts(); + var config = builder.new PostRequestConfiguration(); + Consumer configure = call.getArgument(1); + configure.accept(config); + assertThat(config.queryParameters.ifExists).isEqualTo(IfArtifactExists.CREATE_VERSION); + return new CreateArtifactResponse(); + }); + Object response = template.requestBodyAndHeaders("direct:createArtifact", "{\"type\":\"string\"}", Map.of( + ApicurioRegistryConstants.HEADER_GROUP_ID, "other-group", + ApicurioRegistryConstants.HEADER_ARTIFACT_ID, "other-artifact", + ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "AVRO", + ApicurioRegistryConstants.HEADER_IF_EXISTS, "CREATE_VERSION")); + assertThat(response).isInstanceOf(CreateArtifactResponse.class); + } + @Test void testDeleteArtifact() throws Exception { String endpointUri @@ -145,7 +178,7 @@ void testGetArtifactMetadata() throws Exception { .thenReturn(mockMetadata); Object result = template.requestBody("direct:getArtifactMetadata", (Object) null); - assertEquals(mockMetadata, result); + assertThat(result).isSameAs(mockMetadata); } @Test @@ -154,13 +187,14 @@ void testGetArtifactContent() throws Exception { = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=getArtifactContent"; injectMockClient(endpointUri); - ByteArrayInputStream mockContent = new ByteArrayInputStream("{\"test\":true}".getBytes(StandardCharsets.UTF_8)); + ByteArrayInputStream mockContent = spy(new ByteArrayInputStream("{\"test\":true}".getBytes(StandardCharsets.UTF_8))); when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") .versions().byVersionExpression("branch=latest").content().get()) .thenReturn(mockContent); Object result = template.requestBody("direct:getArtifactContent", (Object) null); - assertNotNull(result); + assertThat(result).isEqualTo("{\"test\":true}".getBytes(StandardCharsets.UTF_8)); + verify(mockContent).close(); } @Test @@ -176,7 +210,7 @@ void testListVersions() throws Exception { .thenReturn(mockResults); Object result = template.requestBody("direct:listVersions", (Object) null); - assertEquals(mockResults, result); + assertThat(result).isSameAs(mockResults); } @Test @@ -189,7 +223,7 @@ void testCreateGroup() throws Exception { when(mockClient.groups().post(any(CreateGroup.class))).thenReturn(mockGroupMeta); Object result = template.requestBody("direct:createGroup", (Object) null); - assertEquals(mockGroupMeta, result); + assertThat(result).isSameAs(mockGroupMeta); } @Test @@ -204,7 +238,7 @@ void testUpdateArtifact() throws Exception { .thenReturn(mockVersionMeta); Object result = template.requestBody("direct:updateArtifact", "{\"updated\":true}"); - assertEquals(mockVersionMeta, result); + assertThat(result).isSameAs(mockVersionMeta); } @Test @@ -219,7 +253,7 @@ void testOperationFromHeader() throws Exception { Object result = template.requestBodyAndHeader("direct:operationFromHeader", null, ApicurioRegistryConstants.HEADER_OPERATION, "getArtifactMetadata"); - assertEquals(mockMetadata, result); + assertThat(result).isSameAs(mockMetadata); } @Test @@ -232,7 +266,7 @@ void testSearchArtifacts() throws Exception { when(mockClient.search().artifacts().get(any())).thenReturn(mockResults); Object result = template.requestBody("direct:searchArtifacts", (Object) null); - assertEquals(mockResults, result); + assertThat(result).isSameAs(mockResults); } @Test @@ -247,7 +281,7 @@ void testTestCompatibilitySuccess() throws Exception { .thenReturn(mockVersion); Object result = template.requestBody("direct:testCompatibility", "{\"test\":true}"); - assertEquals(true, result); + assertThat(result).isEqualTo(true); } @Test @@ -258,10 +292,10 @@ void testTestCompatibilityFailure() throws Exception { when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") .versions().post(any(CreateVersion.class), any())) - .thenThrow(new ApiException("incompatible")); + .thenThrow(ruleViolation("incompatible")); Object result = template.requestBody("direct:testCompatibility", "{\"bad\":true}"); - assertEquals(false, result); + assertThat(result).isEqualTo(false); } @Test @@ -275,8 +309,14 @@ void testValidateSuccess() throws Exception { .versions().post(any(CreateVersion.class), any())) .thenReturn(mockVersion); - var exchange = template.request("direct:validate", ex -> ex.getIn().setBody("{\"test\":true}")); - assertTrue(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, Boolean.class)); + var exchange = template.request("direct:validate", ex -> { + ex.getIn().setBody("{\"test\":true}"); + ex.getIn().setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, "stale error"); + }); + assertThat(exchange.getException()).isNull(); + assertThat(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, Boolean.class)).isTrue(); + assertThat(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)).isNull(); + assertThat(exchange.getIn().getBody()).isEqualTo("{\"test\":true}"); } @Test @@ -287,11 +327,13 @@ void testValidateFailureNoThrow() throws Exception { when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") .versions().post(any(CreateVersion.class), any())) - .thenThrow(new ApiException("validation error")); + .thenThrow(ruleViolation("validation error")); var exchange = template.request("direct:validate", ex -> ex.getIn().setBody("{\"bad\":true}")); - assertFalse(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, Boolean.class)); - assertNotNull(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)); + assertThat(exchange.getException()).isNull(); + assertThat(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, Boolean.class)).isFalse(); + assertThat(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)) + .isEqualTo("validation error"); } @Test @@ -302,12 +344,58 @@ void testValidateFailureThrows() throws Exception { when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") .versions().post(any(CreateVersion.class), any())) - .thenThrow(new ApiException("validation error")); + .thenThrow(ruleViolation("validation error")); - try { - template.requestBody("direct:validateFail", "{\"bad\":true}"); - } catch (CamelExecutionException e) { - assertInstanceOf(ApicurioRegistryValidationException.class, e.getCause()); + assertThatThrownBy(() -> template.requestBody("direct:validateFail", "{\"bad\":true}")) + .isInstanceOf(CamelExecutionException.class) + .hasCauseInstanceOf(ApicurioRegistryValidationException.class); + } + + @ParameterizedTest + @ValueSource(strings = { "testCompatibility", "validate" }) + void testNonValidationFailuresPropagate(String operation) throws Exception { + String uri = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3" + + "&operation=" + operation + ("validate".equals(operation) ? "&failOnValidation=false" : ""); + injectMockClient(uri); + ProblemDetails unauthorized = new ProblemDetails(); + unauthorized.setName("NotAuthorizedException"); + unauthorized.setStatus(401); + ApiException serverError = new ApiException("server error"); + IllegalStateException connectionError = new IllegalStateException("connection failed"); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().post(any(CreateVersion.class), any())) + .thenThrow(unauthorized, serverError, connectionError); + + for (Exception failure : List.of(unauthorized, serverError, connectionError)) { + var exchange = template.request("direct:" + operation, ex -> ex.getIn().setBody("{}")); + assertThat(exchange.getException()).isSameAs(failure); + assertThat(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)).isNull(); } } + + @Test + void testUpdateHeaderOverrides() throws Exception { + injectMockClient("apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3" + + "&operation=updateArtifact"); + template.requestBodyAndHeaders("direct:updateArtifact", "syntax = \"proto3\";", Map.of( + ApicurioRegistryConstants.HEADER_GROUP_ID, "other/group", + ApicurioRegistryConstants.HEADER_ARTIFACT_ID, "other-artifact", + ApicurioRegistryConstants.HEADER_VERSION, "2", + ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/x-protobuf")); + + ArgumentCaptor request = ArgumentCaptor.forClass(CreateVersion.class); + verify(mockClient.groups().byGroupId("other/group").artifacts().byArtifactId("other-artifact").versions()) + .post(request.capture()); + assertThat(request.getValue().getVersion()).isEqualTo("2"); + assertThat(request.getValue().getContent().getContentType()).isEqualTo("application/x-protobuf"); + assertThat(request.getValue().getContent().getContent()).isEqualTo("syntax = \"proto3\";"); + } + + private static RuleViolationProblemDetails ruleViolation(String message) { + RuleViolationProblemDetails error = new RuleViolationProblemDetails(); + error.setName("RuleViolationException"); + error.setStatus(409); + error.setTitle(message); + return error; + } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java index ceadda364943f..bb1a753febfca 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryConsumerIT.java @@ -21,13 +21,14 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; +import org.apache.camel.Exchange; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants; import org.apache.camel.component.mock.MockEndpoint; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; class ApicurioRegistryConsumerIT extends ApicurioRegistryTestSupport { @@ -55,7 +56,7 @@ public void configure() { // Consumer route from("apicurio-registry:" + groupId + "/" + artifactId - + "?registryUrl=" + getRegistryUrl() + "&delay=1000") + + "?registryUrl=" + getRegistryUrl() + "&delay=1000&fetchContent=true") .to("mock:consumed"); } }; @@ -67,7 +68,8 @@ void testConsumerReceivesNewVersions() throws Exception { Map groupHeaders = new HashMap<>(); groupHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_GROUP); groupHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); - template.request("direct:createForConsumer", exchange -> exchange.getIn().setHeaders(groupHeaders)); + Exchange group = template.request("direct:createForConsumer", exchange -> exchange.getIn().setHeaders(groupHeaders)); + assertThat(group.getException()).isNull(); // Create the artifact Map createHeaders = new HashMap<>(); @@ -76,19 +78,35 @@ void testConsumerReceivesNewVersions() throws Exception { createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "JSON"); createHeaders.put(ApicurioRegistryConstants.HEADER_IF_EXISTS, "FIND_OR_CREATE_VERSION"); - template.request("direct:createForConsumer", exchange -> { + Exchange artifact = template.request("direct:createForConsumer", exchange -> { exchange.getIn().setHeaders(createHeaders); exchange.getIn().setBody(JSON_SCHEMA); }); + assertThat(artifact.getException()).isNull(); MockEndpoint mock = getMockEndpoint("mock:consumed"); - mock.expectedMinimumMessageCount(1); + mock.expectedMessageCount(1); MockEndpoint.assertIsSatisfied(context, 30, TimeUnit.SECONDS); // Verify headers set by consumer Map receivedHeaders = mock.getReceivedExchanges().get(0).getIn().getHeaders(); - assertEquals(groupId, receivedHeaders.get(ApicurioRegistryConstants.HEADER_GROUP_ID)); - assertEquals(artifactId, receivedHeaders.get(ApicurioRegistryConstants.HEADER_ARTIFACT_ID)); + assertThat(receivedHeaders.get(ApicurioRegistryConstants.HEADER_GROUP_ID)).isEqualTo(groupId); + assertThat(receivedHeaders.get(ApicurioRegistryConstants.HEADER_ARTIFACT_ID)).isEqualTo(artifactId); + assertThat(mock.getExchanges().get(0).getIn().getBody(String.class)).isEqualTo(JSON_SCHEMA); + + createHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_UPDATE_ARTIFACT); + String secondSchema = "{\"type\":\"string\"}"; + Exchange update = template.request("direct:createForConsumer", exchange -> { + exchange.getIn().setHeaders(createHeaders); + exchange.getIn().setBody(secondSchema); + }); + assertThat(update.getException()).isNull(); + mock.expectedMessageCount(2); + mock.setAssertPeriod(1500); + MockEndpoint.assertIsSatisfied(context, 30, TimeUnit.SECONDS); + assertThat(mock.getExchanges().get(1).getIn().getBody(String.class)).isEqualTo(secondSchema); + assertThat(mock.getExchanges().get(1).getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class)) + .isGreaterThan((Long) receivedHeaders.get(ApicurioRegistryConstants.HEADER_GLOBAL_ID)); } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java index 97681befadc96..de00671454d35 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java @@ -22,8 +22,10 @@ import java.util.UUID; import io.apicurio.registry.rest.client.models.ArtifactMetaData; +import io.apicurio.registry.rest.client.models.ArtifactSearchResults; import io.apicurio.registry.rest.client.models.CreateArtifactResponse; import io.apicurio.registry.rest.client.models.GroupMetaData; +import io.apicurio.registry.rest.client.models.ProblemDetails; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; import org.apache.camel.Exchange; @@ -31,11 +33,10 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; class ApicurioRegistryProducerIT extends ApicurioRegistryTestSupport { @@ -84,8 +85,8 @@ void testFullArtifactLifecycle() throws Exception { Exchange groupResult = template.request("direct:operation", exchange -> { exchange.getIn().setHeaders(headers); }); - assertNotNull(groupResult.getIn().getBody()); - assertInstanceOf(GroupMetaData.class, groupResult.getIn().getBody()); + assertThat(groupResult.getException()).isNull(); + assertThat(groupResult.getIn().getBody()).isInstanceOf(GroupMetaData.class); // 2. Create artifact Map createHeaders = new HashMap<>(); @@ -98,8 +99,8 @@ void testFullArtifactLifecycle() throws Exception { exchange.getIn().setHeaders(createHeaders); exchange.getIn().setBody(JSON_SCHEMA); }); - assertNotNull(createResult.getIn().getBody()); - assertInstanceOf(CreateArtifactResponse.class, createResult.getIn().getBody()); + assertThat(createResult.getException()).isNull(); + assertThat(createResult.getIn().getBody()).isInstanceOf(CreateArtifactResponse.class); // 3. Get artifact metadata Map getMetaHeaders = new HashMap<>(); @@ -110,9 +111,9 @@ void testFullArtifactLifecycle() throws Exception { Exchange metaResult = template.request("direct:operation", exchange -> { exchange.getIn().setHeaders(getMetaHeaders); }); - assertInstanceOf(ArtifactMetaData.class, metaResult.getIn().getBody()); + assertThat(metaResult.getIn().getBody()).isInstanceOf(ArtifactMetaData.class); ArtifactMetaData metadata = (ArtifactMetaData) metaResult.getIn().getBody(); - assertEquals(artifactId, metadata.getArtifactId()); + assertThat(metadata.getArtifactId()).isEqualTo(artifactId); // 4. Get artifact content Map getContentHeaders = new HashMap<>(); @@ -124,9 +125,9 @@ void testFullArtifactLifecycle() throws Exception { exchange.getIn().setHeaders(getContentHeaders); }); byte[] contentBody = contentResult.getIn().getBody(byte[].class); - assertNotNull(contentBody); + assertThat(contentBody).isNotNull(); String contentStr = new String(contentBody, StandardCharsets.UTF_8); - assertTrue(contentStr.contains("name")); + assertThat(contentStr).isEqualTo(JSON_SCHEMA); // 5. Update artifact (new version) Map updateHeaders = new HashMap<>(); @@ -138,7 +139,7 @@ void testFullArtifactLifecycle() throws Exception { exchange.getIn().setHeaders(updateHeaders); exchange.getIn().setBody(JSON_SCHEMA_V2); }); - assertInstanceOf(VersionMetaData.class, updateResult.getIn().getBody()); + assertThat(updateResult.getIn().getBody()).isInstanceOf(VersionMetaData.class); // 6. List versions Map listHeaders = new HashMap<>(); @@ -149,9 +150,9 @@ void testFullArtifactLifecycle() throws Exception { Exchange listResult = template.request("direct:operation", exchange -> { exchange.getIn().setHeaders(listHeaders); }); - assertInstanceOf(VersionSearchResults.class, listResult.getIn().getBody()); + assertThat(listResult.getIn().getBody()).isInstanceOf(VersionSearchResults.class); VersionSearchResults versions = (VersionSearchResults) listResult.getIn().getBody(); - assertTrue(versions.getCount() >= 2); + assertThat(versions.getCount()).isEqualTo(2); // 7. Delete artifact Map deleteHeaders = new HashMap<>(); @@ -159,9 +160,10 @@ void testFullArtifactLifecycle() throws Exception { ApicurioRegistryConstants.OPERATION_DELETE_ARTIFACT); deleteHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); deleteHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); - template.request("direct:operation", exchange -> { + Exchange deleted = template.request("direct:operation", exchange -> { exchange.getIn().setHeaders(deleteHeaders); }); + assertThat(deleted.getException()).isNull(); } @Test @@ -194,13 +196,29 @@ void testSearchArtifacts() throws Exception { Exchange searchResult = template.request("direct:operation", exchange -> { exchange.getIn().setHeaders(searchHeaders); }); - assertNotNull(searchResult.getIn().getBody()); + assertThat(searchResult.getException()).isNull(); + ArtifactSearchResults results = searchResult.getIn().getBody(ArtifactSearchResults.class); + assertThat(results.getArtifacts()).extracting("artifactId").containsExactly(artifactId); // Cleanup Map deleteHeaders = new HashMap<>(); deleteHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_DELETE_ARTIFACT); deleteHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); deleteHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); - template.request("direct:operation", exchange -> exchange.getIn().setHeaders(deleteHeaders)); + Exchange deleted = template.request("direct:operation", exchange -> exchange.getIn().setHeaders(deleteHeaders)); + assertThat(deleted.getException()).isNull(); + } + + @ParameterizedTest + @ValueSource(strings = { "deleteArtifact", "updateArtifact" }) + void testMissingArtifact(String operation) { + Exchange result = template.request("direct:operation", exchange -> { + exchange.getIn().setHeader(ApicurioRegistryConstants.HEADER_OPERATION, operation); + exchange.getIn().setHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, "missing-group-" + UUID.randomUUID()); + exchange.getIn().setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, "missing-artifact"); + exchange.getIn().setBody(JSON_SCHEMA); + }); + assertThat(result.getException()).isInstanceOf(ProblemDetails.class); + assertThat(((ProblemDetails) result.getException()).getStatus()).isEqualTo(404); } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java index d8eeda7414227..cc48fe3a47103 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryValidateIT.java @@ -20,15 +20,20 @@ import java.util.Map; import java.util.UUID; +import io.apicurio.registry.rest.client.models.CreateRule; +import io.apicurio.registry.rest.client.models.RuleType; +import org.apache.camel.CamelExecutionException; import org.apache.camel.Exchange; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryEndpoint; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryValidationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class ApicurioRegistryValidateIT extends ApicurioRegistryTestSupport { @@ -65,6 +70,11 @@ public void configure() { .to("apicurio-registry:" + groupId + "/" + artifactId + "?registryUrl=" + getRegistryUrl() + "&operation=testCompatibility"); + + from("direct:validateFail") + .to("apicurio-registry:" + groupId + "/" + artifactId + + "?registryUrl=" + getRegistryUrl() + + "&operation=validate"); } }; } @@ -75,7 +85,8 @@ void setupArtifact() { Map groupHeaders = new HashMap<>(); groupHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_GROUP); groupHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); - template.request("direct:setup", exchange -> exchange.getIn().setHeaders(groupHeaders)); + Exchange group = template.request("direct:setup", exchange -> exchange.getIn().setHeaders(groupHeaders)); + assertThat(group.getException()).isNull(); // Create artifact with JSON Schema Map createHeaders = new HashMap<>(); @@ -84,10 +95,20 @@ void setupArtifact() { createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "JSON"); createHeaders.put(ApicurioRegistryConstants.HEADER_IF_EXISTS, "FIND_OR_CREATE_VERSION"); - template.request("direct:setup", exchange -> { + Exchange artifact = template.request("direct:setup", exchange -> { exchange.getIn().setHeaders(createHeaders); exchange.getIn().setBody(JSON_SCHEMA); }); + assertThat(artifact.getException()).isNull(); + + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:" + groupId + "/" + artifactId + "?registryUrl=" + getRegistryUrl() + + "&operation=validate", + ApicurioRegistryEndpoint.class); + CreateRule rule = new CreateRule(); + rule.setRuleType(RuleType.COMPATIBILITY); + rule.setConfig("BACKWARD"); + endpoint.getRegistryClient().groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).rules().post(rule); } @Test @@ -98,8 +119,7 @@ void testValidateCompatibleContent() { "type": "object", "properties": { "name": {"type": "string"}, - "age": {"type": "integer"}, - "email": {"type": "string"} + "age": {"type": "number"} }, "required": ["name"] } @@ -109,7 +129,8 @@ void testValidateCompatibleContent() { exchange.getIn().setBody(compatibleSchema); }); - assertEquals(true, result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT)); + assertThat(result.getException()).isNull(); + assertThat(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT)).isEqualTo(true); } @Test @@ -128,6 +149,28 @@ void testTestCompatibility() { exchange.getIn().setBody(compatibleSchema); }); - assertEquals(true, result.getIn().getBody()); + assertThat(result.getException()).isNull(); + assertThat(result.getIn().getBody()).isEqualTo(true); + } + + @Test + void testIncompatibleContent() { + String incompatible = "{\"type\":\"integer\"}"; + Exchange validation = template.request("direct:validate", exchange -> exchange.getIn().setBody(incompatible)); + assertThat(validation.getException()).isNull(); + assertThat(validation.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT)).isEqualTo(false); + assertThat(validation.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, String.class)).isNotBlank(); + assertThat(validation.getIn().getBody()).isEqualTo(incompatible); + + Exchange compatibility + = template.request("direct:testCompatibility", exchange -> exchange.getIn().setBody(incompatible)); + assertThat(compatibility.getException()).isNull(); + assertThat(compatibility.getIn().getBody()).isEqualTo(false); + assertThat(compatibility.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, String.class)) + .isNotBlank(); + + assertThatThrownBy(() -> template.requestBody("direct:validateFail", incompatible)) + .isInstanceOf(CamelExecutionException.class) + .hasCauseInstanceOf(ApicurioRegistryValidationException.class); } } diff --git a/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties b/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties index a72d3a6c6980d..c4510b3bcbb27 100644 --- a/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties +++ b/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties @@ -12,6 +12,7 @@ alibaba-ots alibaba-sls alibaba-sms amqp +apicurio-registry arangodb as2 asterisk diff --git a/docs/components/modules/ROOT/examples/json/apicurio-registry.json b/docs/components/modules/ROOT/examples/json/apicurio-registry.json new file mode 120000 index 0000000000000..2f1692df4e7d7 --- /dev/null +++ b/docs/components/modules/ROOT/examples/json/apicurio-registry.json @@ -0,0 +1 @@ +../../../../../../components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json \ No newline at end of file diff --git a/docs/components/modules/ROOT/nav.adoc b/docs/components/modules/ROOT/nav.adoc index beb1d440dfe5a..246f96a4c38fe 100644 --- a/docs/components/modules/ROOT/nav.adoc +++ b/docs/components/modules/ROOT/nav.adoc @@ -42,6 +42,7 @@ *** xref:alibaba-sls-component.adoc[Alibaba Simple Log Service (SLS)] *** xref:alibaba-ots-component.adoc[Alibaba Tablestore (OTS)] ** xref:amqp-component.adoc[AMQP] +** xref:apicurio-registry-component.adoc[Apicurio Registry] ** xref:arangodb-component.adoc[ArangoDb] ** xref:as2-component.adoc[AS2] ** xref:asterisk-component.adoc[Asterisk] diff --git a/docs/components/modules/ROOT/pages/apicurio-registry-component.adoc b/docs/components/modules/ROOT/pages/apicurio-registry-component.adoc new file mode 120000 index 0000000000000..cac7658d3701c --- /dev/null +++ b/docs/components/modules/ROOT/pages/apicurio-registry-component.adoc @@ -0,0 +1 @@ +../../../../../components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc \ No newline at end of file diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java index 0134a73fbe96e..cc5695bfd6396 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java @@ -232,6 +232,19 @@ static AlibabaSmsComponentBuilderFactory.AlibabaSmsComponentBuilder alibabaSms() static AmqpComponentBuilderFactory.AmqpComponentBuilder amqp() { return AmqpComponentBuilderFactory.amqp(); } + /** + * Apicurio Registry (camel-apicurio-registry) + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Category: cloud,api + * Since: 4.23 + * Maven coordinates: org.apache.camel:camel-apicurio-registry + * + * @return the dsl builder + */ + static ApicurioRegistryComponentBuilderFactory.ApicurioRegistryComponentBuilder apicurioRegistry() { + return ApicurioRegistryComponentBuilderFactory.apicurioRegistry(); + } /** * ArangoDb (camel-arangodb) * Perform operations on ArangoDb when used as a Document Database, or as a diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java new file mode 100644 index 0000000000000..1282b2ea7e34b --- /dev/null +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java @@ -0,0 +1,392 @@ +/* Generated by camel build tools - do NOT edit this file! */ +/* + * 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.builder.component.dsl; + +import javax.annotation.processing.Generated; +import org.apache.camel.Component; +import org.apache.camel.builder.component.AbstractComponentBuilder; +import org.apache.camel.builder.component.ComponentBuilder; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent; + +/** + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.ComponentDslMojo") +public interface ApicurioRegistryComponentBuilderFactory { + + /** + * Apicurio Registry (camel-apicurio-registry) + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Category: cloud,api + * Since: 4.23 + * Maven coordinates: org.apache.camel:camel-apicurio-registry + * + * @return the dsl builder + */ + static ApicurioRegistryComponentBuilder apicurioRegistry() { + return new ApicurioRegistryComponentBuilderImpl(); + } + + /** + * Builder for the Apicurio Registry component. + */ + interface ApicurioRegistryComponentBuilder extends ComponentBuilder { + + /** + * The Apicurio Registry base URL. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: common + * + * @param registryUrl the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder registryUrl(java.lang.String registryUrl) { + doSetProperty("registryUrl", registryUrl); + return this; + } + + + /** + * 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. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: consumer + * + * @param bridgeErrorHandler the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder bridgeErrorHandler(boolean bridgeErrorHandler) { + doSetProperty("bridgeErrorHandler", bridgeErrorHandler); + return this; + } + + + /** + * Whether to fetch the content for each new version found by the + * consumer. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: consumer + * + * @param fetchContent the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder fetchContent(boolean fetchContent) { + doSetProperty("fetchContent", fetchContent); + return this; + } + + + /** + * The default artifact type. + * + * The option is a: <code>java.lang.String</code> type. + * + * Default: JSON + * Group: producer + * + * @param artifactType the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder artifactType(java.lang.String artifactType) { + doSetProperty("artifactType", artifactType); + return this; + } + + + /** + * Whether to throw an exception on validation failure (validate + * operation). When false, sets result headers instead. + * + * The option is a: <code>boolean</code> type. + * + * Default: true + * Group: producer + * + * @param failOnValidation the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder failOnValidation(boolean failOnValidation) { + doSetProperty("failOnValidation", failOnValidation); + return this; + } + + + /** + * Behavior when artifact already exists. + * + * The option is a: <code>java.lang.String</code> type. + * + * Default: FAIL + * Group: producer + * + * @param ifExists the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder ifExists(java.lang.String ifExists) { + doSetProperty("ifExists", ifExists); + return this; + } + + + /** + * 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. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: producer + * + * @param lazyStartProducer the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder lazyStartProducer(boolean lazyStartProducer) { + doSetProperty("lazyStartProducer", lazyStartProducer); + return this; + } + + /** + * The operation to perform. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: producer + * + * @param operation the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder operation(java.lang.String operation) { + doSetProperty("operation", operation); + return this; + } + + + /** + * 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. + * + * The option is a: <code>boolean</code> type. + * + * Default: true + * Group: advanced + * + * @param autowiredEnabled the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder autowiredEnabled(boolean autowiredEnabled) { + doSetProperty("autowiredEnabled", autowiredEnabled); + return this; + } + + /** + * The component configuration. + * + * The option is a: + * <code>org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration</code> type. + * + * Group: advanced + * + * @param configuration the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder configuration(org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration configuration) { + doSetProperty("configuration", configuration); + return this; + } + + + /** + * The authentication type to use. + * + * The option is a: <code>java.lang.String</code> type. + * + * Default: none + * Group: security + * + * @param authType the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder authType(java.lang.String authType) { + doSetProperty("authType", authType); + return this; + } + + /** + * OAuth2 client ID. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param clientId the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder clientId(java.lang.String clientId) { + doSetProperty("clientId", clientId); + return this; + } + + /** + * OAuth2 client secret. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param clientSecret the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder clientSecret(java.lang.String clientSecret) { + doSetProperty("clientSecret", clientSecret); + return this; + } + + /** + * Password for basic authentication. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param password the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder password(java.lang.String password) { + doSetProperty("password", password); + return this; + } + + /** + * OAuth2 scope. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param scope the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder scope(java.lang.String scope) { + doSetProperty("scope", scope); + return this; + } + + /** + * OAuth2 token endpoint URL. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param tokenEndpoint the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder tokenEndpoint(java.lang.String tokenEndpoint) { + doSetProperty("tokenEndpoint", tokenEndpoint); + return this; + } + + /** + * Username for basic authentication. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: security + * + * @param username the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder username(java.lang.String username) { + doSetProperty("username", username); + return this; + } + } + + class ApicurioRegistryComponentBuilderImpl + extends AbstractComponentBuilder + implements ApicurioRegistryComponentBuilder { + @Override + protected ApicurioRegistryComponent buildConcreteComponent() { + return new ApicurioRegistryComponent(); + } + private org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration getOrCreateConfiguration(ApicurioRegistryComponent component) { + if (component.getConfiguration() == null) { + component.setConfiguration(new org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration()); + } + return component.getConfiguration(); + } + @Override + protected boolean setPropertyOnComponent( + Component component, + String name, + Object value) { + switch (name) { + case "registryUrl": getOrCreateConfiguration((ApicurioRegistryComponent) component).setRegistryUrl((java.lang.String) value); return true; + case "bridgeErrorHandler": ((ApicurioRegistryComponent) component).setBridgeErrorHandler((boolean) value); return true; + case "fetchContent": getOrCreateConfiguration((ApicurioRegistryComponent) component).setFetchContent((boolean) value); return true; + case "artifactType": getOrCreateConfiguration((ApicurioRegistryComponent) component).setArtifactType((java.lang.String) value); return true; + case "failOnValidation": getOrCreateConfiguration((ApicurioRegistryComponent) component).setFailOnValidation((boolean) value); return true; + case "ifExists": getOrCreateConfiguration((ApicurioRegistryComponent) component).setIfExists((java.lang.String) value); return true; + case "lazyStartProducer": ((ApicurioRegistryComponent) component).setLazyStartProducer((boolean) value); return true; + case "operation": getOrCreateConfiguration((ApicurioRegistryComponent) component).setOperation((java.lang.String) value); return true; + case "autowiredEnabled": ((ApicurioRegistryComponent) component).setAutowiredEnabled((boolean) value); return true; + case "configuration": ((ApicurioRegistryComponent) component).setConfiguration((org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration) value); return true; + case "authType": getOrCreateConfiguration((ApicurioRegistryComponent) component).setAuthType((java.lang.String) value); return true; + case "clientId": getOrCreateConfiguration((ApicurioRegistryComponent) component).setClientId((java.lang.String) value); return true; + case "clientSecret": getOrCreateConfiguration((ApicurioRegistryComponent) component).setClientSecret((java.lang.String) value); return true; + case "password": getOrCreateConfiguration((ApicurioRegistryComponent) component).setPassword((java.lang.String) value); return true; + case "scope": getOrCreateConfiguration((ApicurioRegistryComponent) component).setScope((java.lang.String) value); return true; + case "tokenEndpoint": getOrCreateConfiguration((ApicurioRegistryComponent) component).setTokenEndpoint((java.lang.String) value); return true; + case "username": getOrCreateConfiguration((ApicurioRegistryComponent) component).setUsername((java.lang.String) value); return true; + default: return false; + } + } + } +} \ No newline at end of file diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilderFactory.java index 3b739d5ed91ac..5e1d3209f6423 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilderFactory.java @@ -42,6 +42,7 @@ public interface EndpointBuilderFactory org.apache.camel.builder.endpoint.dsl.AlibabaEventBridgeEndpointBuilderFactory.AlibabaEventBridgeBuilders, org.apache.camel.builder.endpoint.dsl.AlibabaOtsEndpointBuilderFactory.AlibabaOtsBuilders, org.apache.camel.builder.endpoint.dsl.AlibabaSlsEndpointBuilderFactory.AlibabaSlsBuilders, + org.apache.camel.builder.endpoint.dsl.ApicurioRegistryEndpointBuilderFactory.ApicurioRegistryBuilders, org.apache.camel.builder.endpoint.dsl.ArangoDbEndpointBuilderFactory.ArangoDbBuilders, org.apache.camel.builder.endpoint.dsl.AsteriskEndpointBuilderFactory.AsteriskBuilders, org.apache.camel.builder.endpoint.dsl.Athena2EndpointBuilderFactory.Athena2Builders, diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilders.java index 4585eeaea46a2..6ba759ca18eff 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointBuilders.java @@ -39,6 +39,7 @@ public interface EndpointBuilders org.apache.camel.builder.endpoint.dsl.AlibabaEventBridgeEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.AlibabaOtsEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.AlibabaSlsEndpointBuilderFactory, + org.apache.camel.builder.endpoint.dsl.ApicurioRegistryEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.ArangoDbEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.AsteriskEndpointBuilderFactory, org.apache.camel.builder.endpoint.dsl.Athena2EndpointBuilderFactory, diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java index 507e46a89f832..1a8e2b75aef4b 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java @@ -211,6 +211,19 @@ public static SMSEndpointBuilderFactory.SMSHeaderNameBuilder alibabaSms() { public static AMQPEndpointBuilderFactory.AMQPHeaderNameBuilder amqp() { return AMQPEndpointBuilderFactory.AMQPHeaderNameBuilder.INSTANCE; } + /** + * Apicurio Registry (camel-apicurio-registry) + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Category: cloud,api + * Since: 4.23 + * Maven coordinates: org.apache.camel:camel-apicurio-registry + * + * @return the dsl builder for the headers' name. + */ + public static ApicurioRegistryEndpointBuilderFactory.ApicurioRegistryHeaderNameBuilder apicurioRegistry() { + return ApicurioRegistryEndpointBuilderFactory.ApicurioRegistryHeaderNameBuilder.INSTANCE; + } /** * ArangoDb (camel-arangodb) * Perform operations on ArangoDb when used as a Document Database, or as a diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java index 0de9679652cef..c7d04305be830 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java @@ -656,6 +656,52 @@ public static AMQPEndpointBuilderFactory.AMQPEndpointBuilder amqp(String path) { public static AMQPEndpointBuilderFactory.AMQPEndpointBuilder amqp(String componentName, String path) { return AMQPEndpointBuilderFactory.endpointBuilder(componentName, path); } + /** + * Apicurio Registry (camel-apicurio-registry) + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Category: cloud,api + * Since: 4.23 + * Maven coordinates: org.apache.camel:camel-apicurio-registry + * + * Syntax: apicurio-registry:groupId/artifactId + * + * Path parameter: groupId + * The artifact group ID + * + * Path parameter: artifactId + * The artifact ID + * + * @param path groupId/artifactId + * @return the dsl builder + */ + public static ApicurioRegistryEndpointBuilderFactory.ApicurioRegistryEndpointBuilder apicurioRegistry(String path) { + return apicurioRegistry("apicurio-registry", path); + } + /** + * Apicurio Registry (camel-apicurio-registry) + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Category: cloud,api + * Since: 4.23 + * Maven coordinates: org.apache.camel:camel-apicurio-registry + * + * Syntax: apicurio-registry:groupId/artifactId + * + * Path parameter: groupId + * The artifact group ID + * + * Path parameter: artifactId + * The artifact ID + * + * @param componentName to use a custom component name for the endpoint + * instead of the default name + * @param path groupId/artifactId + * @return the dsl builder + */ + public static ApicurioRegistryEndpointBuilderFactory.ApicurioRegistryEndpointBuilder apicurioRegistry(String componentName, String path) { + return ApicurioRegistryEndpointBuilderFactory.endpointBuilder(componentName, path); + } /** * ArangoDb (camel-arangodb) * Perform operations on ArangoDb when used as a Document Database, or as a diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java new file mode 100644 index 0000000000000..bac634701b76c --- /dev/null +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java @@ -0,0 +1,1588 @@ +/* Generated by camel build tools - do NOT edit this file! */ +/* + * 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.builder.endpoint.dsl; + +import java.util.*; +import java.util.concurrent.*; +import java.util.function.*; +import java.util.stream.*; +import javax.annotation.processing.Generated; +import org.apache.camel.builder.EndpointConsumerBuilder; +import org.apache.camel.builder.EndpointProducerBuilder; +import org.apache.camel.builder.endpoint.AbstractEndpointBuilder; + +/** + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Generated by camel build tools - do NOT edit this file! + */ +@Generated("org.apache.camel.maven.packaging.EndpointDslMojo") +public interface ApicurioRegistryEndpointBuilderFactory { + + /** + * Builder for endpoint consumers for the Apicurio Registry component. + */ + public interface ApicurioRegistryEndpointConsumerBuilder + extends + EndpointConsumerBuilder { + default AdvancedApicurioRegistryEndpointConsumerBuilder advanced() { + return (AdvancedApicurioRegistryEndpointConsumerBuilder) this; + } + /** + * The Apicurio Registry base URL. + * + * The option is a: java.lang.String type. + * + * Required: true + * Group: common + * + * @param registryUrl the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder registryUrl(String registryUrl) { + doSetProperty("registryUrl", registryUrl); + return this; + } + /** + * Whether to fetch the content for each new version found by the + * consumer. + * + * The option is a: boolean type. + * + * Default: false + * Group: consumer + * + * @param fetchContent the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder fetchContent(boolean fetchContent) { + doSetProperty("fetchContent", fetchContent); + return this; + } + /** + * Whether to fetch the content for each new version found by the + * consumer. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: consumer + * + * @param fetchContent the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder fetchContent(String fetchContent) { + doSetProperty("fetchContent", fetchContent); + return this; + } + /** + * If the polling consumer did not poll any files, you can enable this + * option to send an empty message (no body) instead. + * + * The option is a: boolean type. + * + * Default: false + * Group: consumer + * + * @param sendEmptyMessageWhenIdle the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder sendEmptyMessageWhenIdle(boolean sendEmptyMessageWhenIdle) { + doSetProperty("sendEmptyMessageWhenIdle", sendEmptyMessageWhenIdle); + return this; + } + /** + * If the polling consumer did not poll any files, you can enable this + * option to send an empty message (no body) instead. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: consumer + * + * @param sendEmptyMessageWhenIdle the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder sendEmptyMessageWhenIdle(String sendEmptyMessageWhenIdle) { + doSetProperty("sendEmptyMessageWhenIdle", sendEmptyMessageWhenIdle); + return this; + } + /** + * The number of subsequent error polls (failed due some error) that + * should happen before the backoffMultipler should kick-in. + * + * The option is a: int type. + * + * Group: scheduler + * + * @param backoffErrorThreshold the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder backoffErrorThreshold(int backoffErrorThreshold) { + doSetProperty("backoffErrorThreshold", backoffErrorThreshold); + return this; + } + /** + * The number of subsequent error polls (failed due some error) that + * should happen before the backoffMultipler should kick-in. + * + * The option will be converted to a int type. + * + * Group: scheduler + * + * @param backoffErrorThreshold the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder backoffErrorThreshold(String backoffErrorThreshold) { + doSetProperty("backoffErrorThreshold", backoffErrorThreshold); + return this; + } + /** + * The number of subsequent idle polls that should happen before the + * backoffMultipler should kick-in. + * + * The option is a: int type. + * + * Group: scheduler + * + * @param backoffIdleThreshold the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder backoffIdleThreshold(int backoffIdleThreshold) { + doSetProperty("backoffIdleThreshold", backoffIdleThreshold); + return this; + } + /** + * The number of subsequent idle polls that should happen before the + * backoffMultipler should kick-in. + * + * The option will be converted to a int type. + * + * Group: scheduler + * + * @param backoffIdleThreshold the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder backoffIdleThreshold(String backoffIdleThreshold) { + doSetProperty("backoffIdleThreshold", backoffIdleThreshold); + return this; + } + /** + * 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. + * + * The option is a: int type. + * + * Group: scheduler + * + * @param backoffMultiplier the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder backoffMultiplier(int backoffMultiplier) { + doSetProperty("backoffMultiplier", backoffMultiplier); + return this; + } + /** + * 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. + * + * The option will be converted to a int type. + * + * Group: scheduler + * + * @param backoffMultiplier the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder backoffMultiplier(String backoffMultiplier) { + doSetProperty("backoffMultiplier", backoffMultiplier); + return this; + } + /** + * Milliseconds before the next poll. + * + * The option is a: long type. + * + * Default: 500 + * Group: scheduler + * + * @param delay the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder delay(long delay) { + doSetProperty("delay", delay); + return this; + } + /** + * Milliseconds before the next poll. + * + * The option will be converted to a long type. + * + * Default: 500 + * Group: scheduler + * + * @param delay the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder delay(String delay) { + doSetProperty("delay", delay); + return this; + } + /** + * If greedy is enabled, then the ScheduledPollConsumer will run + * immediately again, if the previous run polled 1 or more messages. + * + * The option is a: boolean type. + * + * Default: false + * Group: scheduler + * + * @param greedy the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder greedy(boolean greedy) { + doSetProperty("greedy", greedy); + return this; + } + /** + * If greedy is enabled, then the ScheduledPollConsumer will run + * immediately again, if the previous run polled 1 or more messages. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: scheduler + * + * @param greedy the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder greedy(String greedy) { + doSetProperty("greedy", greedy); + return this; + } + /** + * Milliseconds before the first poll starts. + * + * The option is a: long type. + * + * Default: 1000 + * Group: scheduler + * + * @param initialDelay the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder initialDelay(long initialDelay) { + doSetProperty("initialDelay", initialDelay); + return this; + } + /** + * Milliseconds before the first poll starts. + * + * The option will be converted to a long type. + * + * Default: 1000 + * Group: scheduler + * + * @param initialDelay the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder initialDelay(String initialDelay) { + doSetProperty("initialDelay", initialDelay); + return this; + } + /** + * 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. + * + * The option is a: long type. + * + * Default: 0 + * Group: scheduler + * + * @param repeatCount the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder repeatCount(long repeatCount) { + doSetProperty("repeatCount", repeatCount); + return this; + } + /** + * 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. + * + * The option will be converted to a long type. + * + * Default: 0 + * Group: scheduler + * + * @param repeatCount the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder repeatCount(String repeatCount) { + doSetProperty("repeatCount", repeatCount); + return this; + } + /** + * The consumer logs a start/complete log line when it polls. This + * option allows you to configure the logging level for that. + * + * The option is a: org.apache.camel.LoggingLevel type. + * + * Default: TRACE + * Group: scheduler + * + * @param runLoggingLevel the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder runLoggingLevel(org.apache.camel.LoggingLevel runLoggingLevel) { + doSetProperty("runLoggingLevel", runLoggingLevel); + return this; + } + /** + * The consumer logs a start/complete log line when it polls. This + * option allows you to configure the logging level for that. + * + * The option will be converted to a + * org.apache.camel.LoggingLevel type. + * + * Default: TRACE + * Group: scheduler + * + * @param runLoggingLevel the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder runLoggingLevel(String runLoggingLevel) { + doSetProperty("runLoggingLevel", runLoggingLevel); + return this; + } + /** + * Allows for configuring a custom/shared thread pool to use for the + * consumer. By default each consumer has its own single threaded thread + * pool. + * + * The option is a: + * java.util.concurrent.ScheduledExecutorService type. + * + * Group: scheduler + * + * @param scheduledExecutorService the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder scheduledExecutorService(ScheduledExecutorService scheduledExecutorService) { + doSetProperty("scheduledExecutorService", scheduledExecutorService); + return this; + } + /** + * Allows for configuring a custom/shared thread pool to use for the + * consumer. By default each consumer has its own single threaded thread + * pool. + * + * The option will be converted to a + * java.util.concurrent.ScheduledExecutorService type. + * + * Group: scheduler + * + * @param scheduledExecutorService the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder scheduledExecutorService(String scheduledExecutorService) { + doSetProperty("scheduledExecutorService", scheduledExecutorService); + return this; + } + /** + * To use a cron scheduler from either camel-spring or camel-quartz + * component. Use value spring or quartz for built in scheduler. + * + * The option is a: java.lang.Object type. + * + * Default: none + * Group: scheduler + * + * @param scheduler the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder scheduler(Object scheduler) { + doSetProperty("scheduler", scheduler); + return this; + } + /** + * To use a cron scheduler from either camel-spring or camel-quartz + * component. Use value spring or quartz for built in scheduler. + * + * The option will be converted to a java.lang.Object type. + * + * Default: none + * Group: scheduler + * + * @param scheduler the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder scheduler(String scheduler) { + doSetProperty("scheduler", scheduler); + return this; + } + /** + * 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. + * + * The option is a: java.util.Map<java.lang.String, + * java.lang.Object> type. + * The option is multivalued, and you can use the + * schedulerProperties(String, Object) method to add a value (call the + * method multiple times to set more values). + * + * Group: scheduler + * + * @param key the option key + * @param value the option value + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder schedulerProperties(String key, Object value) { + doSetMultiValueProperty("schedulerProperties", "scheduler." + key, value); + return this; + } + /** + * 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. + * + * The option is a: java.util.Map<java.lang.String, + * java.lang.Object> type. + * The option is multivalued, and you can use the + * schedulerProperties(String, Object) method to add a value (call the + * method multiple times to set more values). + * + * Group: scheduler + * + * @param values the values + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder schedulerProperties(Map values) { + doSetMultiValueProperties("schedulerProperties", "scheduler.", values); + return this; + } + /** + * Whether the scheduler should be auto started. + * + * The option is a: boolean type. + * + * Default: true + * Group: scheduler + * + * @param startScheduler the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder startScheduler(boolean startScheduler) { + doSetProperty("startScheduler", startScheduler); + return this; + } + /** + * Whether the scheduler should be auto started. + * + * The option will be converted to a boolean type. + * + * Default: true + * Group: scheduler + * + * @param startScheduler the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder startScheduler(String startScheduler) { + doSetProperty("startScheduler", startScheduler); + return this; + } + /** + * Time unit for initialDelay and delay options. + * + * The option is a: java.util.concurrent.TimeUnit type. + * + * Default: MILLISECONDS + * Group: scheduler + * + * @param timeUnit the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder timeUnit(TimeUnit timeUnit) { + doSetProperty("timeUnit", timeUnit); + return this; + } + /** + * Time unit for initialDelay and delay options. + * + * The option will be converted to a + * java.util.concurrent.TimeUnit type. + * + * Default: MILLISECONDS + * Group: scheduler + * + * @param timeUnit the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder timeUnit(String timeUnit) { + doSetProperty("timeUnit", timeUnit); + return this; + } + /** + * Controls if fixed delay or fixed rate is used. See + * ScheduledExecutorService in JDK for details. + * + * The option is a: boolean type. + * + * Default: true + * Group: scheduler + * + * @param useFixedDelay the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder useFixedDelay(boolean useFixedDelay) { + doSetProperty("useFixedDelay", useFixedDelay); + return this; + } + /** + * Controls if fixed delay or fixed rate is used. See + * ScheduledExecutorService in JDK for details. + * + * The option will be converted to a boolean type. + * + * Default: true + * Group: scheduler + * + * @param useFixedDelay the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder useFixedDelay(String useFixedDelay) { + doSetProperty("useFixedDelay", useFixedDelay); + return this; + } + /** + * The authentication type to use. + * + * The option is a: java.lang.String type. + * + * Default: none + * Group: security + * + * @param authType the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder authType(String authType) { + doSetProperty("authType", authType); + return this; + } + /** + * OAuth2 client ID. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param clientId the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder clientId(String clientId) { + doSetProperty("clientId", clientId); + return this; + } + /** + * OAuth2 client secret. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param clientSecret the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder clientSecret(String clientSecret) { + doSetProperty("clientSecret", clientSecret); + return this; + } + /** + * Password for basic authentication. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param password the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder password(String password) { + doSetProperty("password", password); + return this; + } + /** + * OAuth2 scope. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param scope the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder scope(String scope) { + doSetProperty("scope", scope); + return this; + } + /** + * OAuth2 token endpoint URL. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param tokenEndpoint the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder tokenEndpoint(String tokenEndpoint) { + doSetProperty("tokenEndpoint", tokenEndpoint); + return this; + } + /** + * Username for basic authentication. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param username the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder username(String username) { + doSetProperty("username", username); + return this; + } + } + + /** + * Advanced builder for endpoint consumers for the Apicurio Registry component. + */ + public interface AdvancedApicurioRegistryEndpointConsumerBuilder + extends + EndpointConsumerBuilder { + default ApicurioRegistryEndpointConsumerBuilder basic() { + return (ApicurioRegistryEndpointConsumerBuilder) this; + } + /** + * 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. + * + * The option is a: boolean type. + * + * Default: false + * Group: consumer (advanced) + * + * @param bridgeErrorHandler the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder bridgeErrorHandler(boolean bridgeErrorHandler) { + doSetProperty("bridgeErrorHandler", bridgeErrorHandler); + return this; + } + /** + * 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. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: consumer (advanced) + * + * @param bridgeErrorHandler the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder bridgeErrorHandler(String bridgeErrorHandler) { + doSetProperty("bridgeErrorHandler", bridgeErrorHandler); + return this; + } + /** + * 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. + * + * The option is a: org.apache.camel.spi.ExceptionHandler + * type. + * + * Group: consumer (advanced) + * + * @param exceptionHandler the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder exceptionHandler(org.apache.camel.spi.ExceptionHandler exceptionHandler) { + doSetProperty("exceptionHandler", exceptionHandler); + return this; + } + /** + * 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. + * + * The option will be converted to a + * org.apache.camel.spi.ExceptionHandler type. + * + * Group: consumer (advanced) + * + * @param exceptionHandler the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder exceptionHandler(String exceptionHandler) { + doSetProperty("exceptionHandler", exceptionHandler); + return this; + } + /** + * Sets the exchange pattern when the consumer creates an exchange. + * + * The option is a: org.apache.camel.ExchangePattern type. + * + * Group: consumer (advanced) + * + * @param exchangePattern the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder exchangePattern(org.apache.camel.ExchangePattern exchangePattern) { + doSetProperty("exchangePattern", exchangePattern); + return this; + } + /** + * Sets the exchange pattern when the consumer creates an exchange. + * + * The option will be converted to a + * org.apache.camel.ExchangePattern type. + * + * Group: consumer (advanced) + * + * @param exchangePattern the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder exchangePattern(String exchangePattern) { + doSetProperty("exchangePattern", exchangePattern); + return this; + } + /** + * 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. + * + * The option is a: + * org.apache.camel.spi.PollingConsumerPollStrategy type. + * + * Group: consumer (advanced) + * + * @param pollStrategy the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder pollStrategy(org.apache.camel.spi.PollingConsumerPollStrategy pollStrategy) { + doSetProperty("pollStrategy", pollStrategy); + return this; + } + /** + * 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. + * + * The option will be converted to a + * org.apache.camel.spi.PollingConsumerPollStrategy type. + * + * Group: consumer (advanced) + * + * @param pollStrategy the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder pollStrategy(String pollStrategy) { + doSetProperty("pollStrategy", pollStrategy); + return this; + } + /** + * To use a pre-configured RegistryClient instance. + * + * The option is a: + * io.apicurio.registry.rest.client.RegistryClient type. + * + * Group: advanced + * + * @param registryClient the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder registryClient(io.apicurio.registry.rest.client.RegistryClient registryClient) { + doSetProperty("registryClient", registryClient); + return this; + } + /** + * To use a pre-configured RegistryClient instance. + * + * The option will be converted to a + * io.apicurio.registry.rest.client.RegistryClient type. + * + * Group: advanced + * + * @param registryClient the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointConsumerBuilder registryClient(String registryClient) { + doSetProperty("registryClient", registryClient); + return this; + } + } + + /** + * Builder for endpoint producers for the Apicurio Registry component. + */ + public interface ApicurioRegistryEndpointProducerBuilder + extends + EndpointProducerBuilder { + default AdvancedApicurioRegistryEndpointProducerBuilder advanced() { + return (AdvancedApicurioRegistryEndpointProducerBuilder) this; + } + + /** + * The Apicurio Registry base URL. + * + * The option is a: java.lang.String type. + * + * Required: true + * Group: common + * + * @param registryUrl the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder registryUrl(String registryUrl) { + doSetProperty("registryUrl", registryUrl); + return this; + } + /** + * The default artifact type. + * + * The option is a: java.lang.String type. + * + * Default: JSON + * Group: producer + * + * @param artifactType the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder artifactType(String artifactType) { + doSetProperty("artifactType", artifactType); + return this; + } + /** + * Whether to throw an exception on validation failure (validate + * operation). When false, sets result headers instead. + * + * The option is a: boolean type. + * + * Default: true + * Group: producer + * + * @param failOnValidation the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder failOnValidation(boolean failOnValidation) { + doSetProperty("failOnValidation", failOnValidation); + return this; + } + /** + * Whether to throw an exception on validation failure (validate + * operation). When false, sets result headers instead. + * + * The option will be converted to a boolean type. + * + * Default: true + * Group: producer + * + * @param failOnValidation the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder failOnValidation(String failOnValidation) { + doSetProperty("failOnValidation", failOnValidation); + return this; + } + /** + * Behavior when artifact already exists. + * + * The option is a: java.lang.String type. + * + * Default: FAIL + * Group: producer + * + * @param ifExists the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder ifExists(String ifExists) { + doSetProperty("ifExists", ifExists); + return this; + } + /** + * The operation to perform. + * + * The option is a: java.lang.String type. + * + * Group: producer + * + * @param operation the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder operation(String operation) { + doSetProperty("operation", operation); + return this; + } + /** + * The authentication type to use. + * + * The option is a: java.lang.String type. + * + * Default: none + * Group: security + * + * @param authType the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder authType(String authType) { + doSetProperty("authType", authType); + return this; + } + /** + * OAuth2 client ID. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param clientId the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder clientId(String clientId) { + doSetProperty("clientId", clientId); + return this; + } + /** + * OAuth2 client secret. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param clientSecret the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder clientSecret(String clientSecret) { + doSetProperty("clientSecret", clientSecret); + return this; + } + /** + * Password for basic authentication. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param password the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder password(String password) { + doSetProperty("password", password); + return this; + } + /** + * OAuth2 scope. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param scope the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder scope(String scope) { + doSetProperty("scope", scope); + return this; + } + /** + * OAuth2 token endpoint URL. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param tokenEndpoint the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder tokenEndpoint(String tokenEndpoint) { + doSetProperty("tokenEndpoint", tokenEndpoint); + return this; + } + /** + * Username for basic authentication. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param username the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder username(String username) { + doSetProperty("username", username); + return this; + } + } + + /** + * Advanced builder for endpoint producers for the Apicurio Registry component. + */ + public interface AdvancedApicurioRegistryEndpointProducerBuilder extends EndpointProducerBuilder { + default ApicurioRegistryEndpointProducerBuilder basic() { + return (ApicurioRegistryEndpointProducerBuilder) this; + } + + /** + * 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. + * + * The option is a: boolean type. + * + * Default: false + * Group: producer (advanced) + * + * @param lazyStartProducer the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointProducerBuilder lazyStartProducer(boolean lazyStartProducer) { + doSetProperty("lazyStartProducer", lazyStartProducer); + return this; + } + /** + * 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. + * + * The option will be converted to a boolean type. + * + * Default: false + * Group: producer (advanced) + * + * @param lazyStartProducer the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointProducerBuilder lazyStartProducer(String lazyStartProducer) { + doSetProperty("lazyStartProducer", lazyStartProducer); + return this; + } + /** + * To use a pre-configured RegistryClient instance. + * + * The option is a: + * io.apicurio.registry.rest.client.RegistryClient type. + * + * Group: advanced + * + * @param registryClient the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointProducerBuilder registryClient(io.apicurio.registry.rest.client.RegistryClient registryClient) { + doSetProperty("registryClient", registryClient); + return this; + } + /** + * To use a pre-configured RegistryClient instance. + * + * The option will be converted to a + * io.apicurio.registry.rest.client.RegistryClient type. + * + * Group: advanced + * + * @param registryClient the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointProducerBuilder registryClient(String registryClient) { + doSetProperty("registryClient", registryClient); + return this; + } + } + + /** + * Builder for endpoint for the Apicurio Registry component. + */ + public interface ApicurioRegistryEndpointBuilder + extends + ApicurioRegistryEndpointConsumerBuilder, + ApicurioRegistryEndpointProducerBuilder { + default AdvancedApicurioRegistryEndpointBuilder advanced() { + return (AdvancedApicurioRegistryEndpointBuilder) this; + } + + /** + * The Apicurio Registry base URL. + * + * The option is a: java.lang.String type. + * + * Required: true + * Group: common + * + * @param registryUrl the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder registryUrl(String registryUrl) { + doSetProperty("registryUrl", registryUrl); + return this; + } + /** + * The authentication type to use. + * + * The option is a: java.lang.String type. + * + * Default: none + * Group: security + * + * @param authType the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder authType(String authType) { + doSetProperty("authType", authType); + return this; + } + /** + * OAuth2 client ID. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param clientId the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder clientId(String clientId) { + doSetProperty("clientId", clientId); + return this; + } + /** + * OAuth2 client secret. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param clientSecret the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder clientSecret(String clientSecret) { + doSetProperty("clientSecret", clientSecret); + return this; + } + /** + * Password for basic authentication. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param password the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder password(String password) { + doSetProperty("password", password); + return this; + } + /** + * OAuth2 scope. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param scope the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder scope(String scope) { + doSetProperty("scope", scope); + return this; + } + /** + * OAuth2 token endpoint URL. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param tokenEndpoint the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder tokenEndpoint(String tokenEndpoint) { + doSetProperty("tokenEndpoint", tokenEndpoint); + return this; + } + /** + * Username for basic authentication. + * + * The option is a: java.lang.String type. + * + * Group: security + * + * @param username the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder username(String username) { + doSetProperty("username", username); + return this; + } + } + + /** + * Advanced builder for endpoint for the Apicurio Registry component. + */ + public interface AdvancedApicurioRegistryEndpointBuilder + extends + AdvancedApicurioRegistryEndpointConsumerBuilder, + AdvancedApicurioRegistryEndpointProducerBuilder { + default ApicurioRegistryEndpointBuilder basic() { + return (ApicurioRegistryEndpointBuilder) this; + } + + /** + * To use a pre-configured RegistryClient instance. + * + * The option is a: + * io.apicurio.registry.rest.client.RegistryClient type. + * + * Group: advanced + * + * @param registryClient the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointBuilder registryClient(io.apicurio.registry.rest.client.RegistryClient registryClient) { + doSetProperty("registryClient", registryClient); + return this; + } + /** + * To use a pre-configured RegistryClient instance. + * + * The option will be converted to a + * io.apicurio.registry.rest.client.RegistryClient type. + * + * Group: advanced + * + * @param registryClient the value to set + * @return the dsl builder + */ + default AdvancedApicurioRegistryEndpointBuilder registryClient(String registryClient) { + doSetProperty("registryClient", registryClient); + return this; + } + } + + public interface ApicurioRegistryBuilders { + /** + * Apicurio Registry (camel-apicurio-registry) + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Category: cloud,api + * Since: 4.23 + * Maven coordinates: org.apache.camel:camel-apicurio-registry + * + * @return the dsl builder for the headers' name. + */ + default ApicurioRegistryHeaderNameBuilder apicurioRegistry() { + return ApicurioRegistryHeaderNameBuilder.INSTANCE; + } + /** + * Apicurio Registry (camel-apicurio-registry) + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Category: cloud,api + * Since: 4.23 + * Maven coordinates: org.apache.camel:camel-apicurio-registry + * + * Syntax: apicurio-registry:groupId/artifactId + * + * Path parameter: groupId + * The artifact group ID + * + * Path parameter: artifactId + * The artifact ID + * + * @param path groupId/artifactId + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder apicurioRegistry(String path) { + return ApicurioRegistryEndpointBuilderFactory.endpointBuilder("apicurio-registry", path); + } + /** + * Apicurio Registry (camel-apicurio-registry) + * Manage artifacts, versions, and groups in Apicurio Registry v3. + * + * Category: cloud,api + * Since: 4.23 + * Maven coordinates: org.apache.camel:camel-apicurio-registry + * + * Syntax: apicurio-registry:groupId/artifactId + * + * Path parameter: groupId + * The artifact group ID + * + * Path parameter: artifactId + * The artifact ID + * + * @param componentName to use a custom component name for the endpoint + * instead of the default name + * @param path groupId/artifactId + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder apicurioRegistry(String componentName, String path) { + return ApicurioRegistryEndpointBuilderFactory.endpointBuilder(componentName, path); + } + + } + /** + * The builder of headers' name for the Apicurio Registry component. + */ + public static class ApicurioRegistryHeaderNameBuilder { + /** + * The internal instance of the builder used to access to all the + * methods representing the name of headers. + */ + public static final ApicurioRegistryHeaderNameBuilder INSTANCE = new ApicurioRegistryHeaderNameBuilder(); + + /** + * The operation to perform. + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code ApicurioRegistryOperation}. + */ + public String apicurioRegistryOperation() { + return "CamelApicurioRegistryOperation"; + } + /** + * The artifact group ID. + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code ApicurioRegistryGroupId}. + */ + public String apicurioRegistryGroupId() { + return "CamelApicurioRegistryGroupId"; + } + /** + * The artifact ID. + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code ApicurioRegistryArtifactId}. + */ + public String apicurioRegistryArtifactId() { + return "CamelApicurioRegistryArtifactId"; + } + /** + * The artifact type (e.g. AVRO, PROTOBUF, JSON, OPENAPI). + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code ApicurioRegistryArtifactType}. + */ + public String apicurioRegistryArtifactType() { + return "CamelApicurioRegistryArtifactType"; + } + /** + * The artifact version expression. + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code ApicurioRegistryVersion}. + */ + public String apicurioRegistryVersion() { + return "CamelApicurioRegistryVersion"; + } + /** + * The artifact name. + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code ApicurioRegistryArtifactName}. + */ + public String apicurioRegistryArtifactName() { + return "CamelApicurioRegistryArtifactName"; + } + /** + * The artifact description. + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code + * ApicurioRegistryArtifactDescription}. + */ + public String apicurioRegistryArtifactDescription() { + return "CamelApicurioRegistryArtifactDescription"; + } + /** + * Behavior when artifact already exists (FAIL, CREATE_VERSION, + * FIND_OR_CREATE_VERSION). + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code ApicurioRegistryIfExists}. + */ + public String apicurioRegistryIfExists() { + return "CamelApicurioRegistryIfExists"; + } + /** + * The content type of the artifact. + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code ApicurioRegistryContentType}. + */ + public String apicurioRegistryContentType() { + return "CamelApicurioRegistryContentType"; + } + /** + * The version global ID. + * + * The option is a: {@code Long} type. + * + * Group: consumer + * + * @return the name of the header {@code ApicurioRegistryGlobalId}. + */ + public String apicurioRegistryGlobalId() { + return "CamelApicurioRegistryGlobalId"; + } + /** + * The content ID. + * + * The option is a: {@code Long} type. + * + * Group: consumer + * + * @return the name of the header {@code ApicurioRegistryContentId}. + */ + public String apicurioRegistryContentId() { + return "CamelApicurioRegistryContentId"; + } + /** + * The version state. + * + * The option is a: {@code String} type. + * + * Group: consumer + * + * @return the name of the header {@code ApicurioRegistryVersionState}. + */ + public String apicurioRegistryVersionState() { + return "CamelApicurioRegistryVersionState"; + } + /** + * Whether validation passed. + * + * The option is a: {@code Boolean} type. + * + * Group: common + * + * @return the name of the header {@code + * ApicurioRegistryValidationResult}. + */ + public String apicurioRegistryValidationResult() { + return "CamelApicurioRegistryValidationResult"; + } + /** + * Validation error details. + * + * The option is a: {@code String} type. + * + * Group: common + * + * @return the name of the header {@code + * ApicurioRegistryValidationErrors}. + */ + public String apicurioRegistryValidationErrors() { + return "CamelApicurioRegistryValidationErrors"; + } + } + static ApicurioRegistryEndpointBuilder endpointBuilder(String componentName, String path) { + class ApicurioRegistryEndpointBuilderImpl extends AbstractEndpointBuilder implements ApicurioRegistryEndpointBuilder, AdvancedApicurioRegistryEndpointBuilder { + public ApicurioRegistryEndpointBuilderImpl(String path) { + super(componentName, path); + } + } + return new ApicurioRegistryEndpointBuilderImpl(path); + } +} \ No newline at end of file diff --git a/dsl/camel-kamelet-main/src/generated/resources/camel-component-known-dependencies.properties b/dsl/camel-kamelet-main/src/generated/resources/camel-component-known-dependencies.properties index 46308c3243ed3..16665c3752ebe 100644 --- a/dsl/camel-kamelet-main/src/generated/resources/camel-component-known-dependencies.properties +++ b/dsl/camel-kamelet-main/src/generated/resources/camel-component-known-dependencies.properties @@ -31,6 +31,7 @@ org.apache.camel.component.alibaba.ots.AlibabaOtsComponent=camel:alibaba-ots org.apache.camel.component.alibaba.sls.AlibabaSlsComponent=camel:alibaba-sls org.apache.camel.component.alibaba.sms.SMSComponent=camel:alibaba-sms org.apache.camel.component.amqp.AMQPComponent=camel:amqp +org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent=camel:apicurio-registry org.apache.camel.component.arangodb.ArangoDbComponent=camel:arangodb org.apache.camel.component.as2.AS2Component=camel:as2 org.apache.camel.component.asterisk.AsteriskComponent=camel:asterisk diff --git a/parent/pom.xml b/parent/pom.xml index 628940f6118de..d97283366e456 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -3423,6 +3423,13 @@ ${jspecify-version} + + + io.apicurio + apicurio-registry-java-sdk + ${apicurio-registry-sdk-version} + + org.slf4j diff --git a/test-infra/camel-test-infra-apicurio-registry/pom.xml b/test-infra/camel-test-infra-apicurio-registry/pom.xml index 5114b2b8f7e36..0151eaab6a59e 100644 --- a/test-infra/camel-test-infra-apicurio-registry/pom.xml +++ b/test-infra/camel-test-infra-apicurio-registry/pom.xml @@ -22,7 +22,7 @@ camel-test-infra-parent org.apache.camel ../camel-test-infra-parent/pom.xml - 4.22.0-SNAPSHOT + 4.23.0-SNAPSHOT 4.0.0 diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java index 103e6403297c1..4646f86969687 100644 --- a/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/java/org/apache/camel/test/infra/apicurio/registry/services/ApicurioRegistryLocalContainerInfraService.java @@ -56,6 +56,7 @@ protected GenericContainer initContainer(String imageName, String containerNa return new GenericContainer<>(imageName) .withNetworkAliases(containerName) .withExposedPorts(ApicurioRegistryProperties.DEFAULT_PORT) + .withEnv("APICURIO_REST_DELETION_ARTIFACT_ENABLED", "true") .waitingFor(Wait.forHttp("/apis/registry/v3/system/info") .forPort(ApicurioRegistryProperties.DEFAULT_PORT).forStatusCode(200)); } From 1e0f656c7492e49742bf93f76a32c92f0e1322f3 Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Wed, 16 Sep 2026 12:11:31 +0200 Subject: [PATCH 06/13] CAMEL-24172: Release polled exchanges and clarify compatibility errors Use manual pooled exchange release on success and failure, preserve retries after failed processing, normalize empty group IDs, and document compatibility error headers with regression coverage. Co-authored-by: GitHub Copilot --- .../catalog/components/apicurio-registry.json | 2 +- .../docs/apicurio-registry-component.adoc | 9 +++ .../apicurioregistry/apicurio-registry.json | 2 +- .../docs/apicurio-registry-component.adoc | 9 +++ .../ApicurioRegistryComponent.java | 2 +- .../ApicurioRegistryConstants.java | 3 +- .../ApicurioRegistryConsumer.java | 50 +++++++------- .../ApicurioRegistryComponentTest.java | 8 +++ .../ApicurioRegistryConsumerTest.java | 67 ++++++++++++++++++- .../ApicurioRegistryProducerTest.java | 15 +++-- ...picurioRegistryEndpointBuilderFactory.java | 3 +- 11 files changed, 135 insertions(+), 35 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json index 5e65924a46600..9e0dc79b3be7b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json @@ -56,7 +56,7 @@ "CamelApicurioRegistryContentId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, "CamelApicurioRegistryVersionState": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether validation passed", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, - "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Validation error details", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } + "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Rule violation details from validate or testCompatibility. Cleared before each check.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } }, "properties": { "groupId": { "index": 0, "kind": "path", "displayName": "Group Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact group ID" }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc index 7ac8c239c242d..3bf96841c9b75 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc @@ -82,6 +82,11 @@ configured rules without storing it. They do not validate application messages a stored schema. Authentication, connectivity, and other non-rule failures propagate as exceptions, rather than returning a negative validation result. +Both operations set `CamelApicurioRegistryValidationErrors` to the rule violation details +when a check fails and clear any previous value before checking. `testCompatibility` returns +the boolean result in the body; `validate` preserves the body and sets +`CamelApicurioRegistryValidationResult`. + == Consumer The consumer polls the registry for new versions of the specified artifact. It tracks the last @@ -92,6 +97,10 @@ On first start, all existing versions of the artifact are emitted as exchanges. only deliver versions created after the consumer started. The watermark is held in memory and resets on route/context restart. +If fetching content or invoking the processor throws an exception, the consumer releases +the exchange and stops the current poll without advancing the watermark for that version. +The failed version remains eligible for the next poll. + When `fetchContent=true`, the consumer fetches the version content and sets it as a `byte[]` exchange body, closing the SDK response stream before processing the exchange. Otherwise, the version metadata object is set as the body. diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json index 5e65924a46600..9e0dc79b3be7b 100644 --- a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json @@ -56,7 +56,7 @@ "CamelApicurioRegistryContentId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, "CamelApicurioRegistryVersionState": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether validation passed", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, - "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Validation error details", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } + "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Rule violation details from validate or testCompatibility. Cleared before each check.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } }, "properties": { "groupId": { "index": 0, "kind": "path", "displayName": "Group Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact group ID" }, diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc index 7ac8c239c242d..3bf96841c9b75 100644 --- a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -82,6 +82,11 @@ configured rules without storing it. They do not validate application messages a stored schema. Authentication, connectivity, and other non-rule failures propagate as exceptions, rather than returning a negative validation result. +Both operations set `CamelApicurioRegistryValidationErrors` to the rule violation details +when a check fails and clear any previous value before checking. `testCompatibility` returns +the boolean result in the body; `validate` preserves the body and sets +`CamelApicurioRegistryValidationResult`. + == Consumer The consumer polls the registry for new versions of the specified artifact. It tracks the last @@ -92,6 +97,10 @@ On first start, all existing versions of the artifact are emitted as exchanges. only deliver versions created after the consumer started. The watermark is held in memory and resets on route/context restart. +If fetching content or invoking the processor throws an exception, the consumer releases +the exchange and stops the current poll without advancing the watermark for that version. +The failed version remains eligible for the next poll. + When `fetchContent=true`, the consumer fetches the version content and sets it as a `byte[]` exchange body, closing the SDK response stream before processing the exchange. Otherwise, the version metadata object is set as the body. diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java index 7d84b31de215e..cb13c9e825de0 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java @@ -45,7 +45,7 @@ protected Endpoint createEndpoint(String uri, String remaining, Map 1 && !parts[1].isEmpty()) { artifactId = parts[1]; } diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java index 00e669dbdf5f0..688d273c5fc7e 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java @@ -60,7 +60,8 @@ public interface ApicurioRegistryConstants { @Metadata(description = "Whether validation passed", javaType = "Boolean") String HEADER_VALIDATION_RESULT = "CamelApicurioRegistryValidationResult"; - @Metadata(description = "Validation error details", javaType = "String") + @Metadata(description = "Rule violation details from validate or testCompatibility. Cleared before each check.", + javaType = "String") String HEADER_VALIDATION_ERRORS = "CamelApicurioRegistryValidationErrors"; String OPERATION_CREATE_ARTIFACT = "createArtifact"; diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java index 9e49b9273054c..aee3a983bc38b 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java @@ -67,33 +67,37 @@ protected int poll() throws Exception { for (SearchedVersion version : versions) { Long globalId = version.getGlobalId(); if (lastSeenGlobalId == null || globalId > lastSeenGlobalId) { - Exchange exchange = createExchange(true); - Message message = exchange.getIn(); + Exchange exchange = createExchange(false); + try { + Message message = exchange.getIn(); - message.setHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); - message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); - message.setHeader(ApicurioRegistryConstants.HEADER_VERSION, version.getVersion()); - message.setHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, globalId); - message.setHeader(ApicurioRegistryConstants.HEADER_CONTENT_ID, version.getContentId()); - message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, version.getArtifactType()); - if (version.getState() != null) { - message.setHeader(ApicurioRegistryConstants.HEADER_VERSION_STATE, - version.getState().getValue()); - } + message.setHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + message.setHeader(ApicurioRegistryConstants.HEADER_VERSION, version.getVersion()); + message.setHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, globalId); + message.setHeader(ApicurioRegistryConstants.HEADER_CONTENT_ID, version.getContentId()); + message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, version.getArtifactType()); + if (version.getState() != null) { + message.setHeader(ApicurioRegistryConstants.HEADER_VERSION_STATE, + version.getState().getValue()); + } - if (configuration.isFetchContent()) { - try (InputStream content = client.groups().byGroupId(groupId).artifacts() - .byArtifactId(artifactId).versions() - .byVersionExpression(version.getVersion()).content().get()) { - message.setBody(content.readAllBytes()); + if (configuration.isFetchContent()) { + try (InputStream content = client.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions() + .byVersionExpression(version.getVersion()).content().get()) { + message.setBody(content.readAllBytes()); + } + } else { + message.setBody(version); } - } else { - message.setBody(version); - } - getProcessor().process(exchange); - lastSeenGlobalId = globalId; - count++; + getProcessor().process(exchange); + lastSeenGlobalId = globalId; + count++; + } finally { + releaseExchange(exchange, false); + } } } return count; diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java index 4712390e9f81a..b6104f4a00ede 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java @@ -23,6 +23,14 @@ class ApicurioRegistryComponentTest extends CamelTestSupport { + @Test + void testEmptyGroupIdNormalized() throws Exception { + ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getComponent("apicurio-registry") + .createEndpoint("apicurio-registry:/someArtifact?registryUrl=http://localhost:8080/apis/registry/v3"); + assertThat(endpoint.getGroupId()).isNull(); + assertThat(endpoint.getArtifactId()).isEqualTo("someArtifact"); + } + @Test void testEndpointCreatedWithGroupAndArtifact() throws Exception { ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint( diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java index be89c3b7b236b..c4b7c8ee37640 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -30,9 +30,12 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.impl.engine.PooledExchangeFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -196,14 +199,72 @@ void testFetchContentClosesStream() throws Exception { verify(stream).close(); } - @Test - void testMissingArtifactId() throws Exception { + @ParameterizedTest + @ValueSource(strings = { "testGroup", "testGroup/", "/someArtifact" }) + void testMissingIdentifier(String path) throws Exception { ApicurioRegistryEndpoint endpoint = context.getEndpoint( - "apicurio-registry:testGroup?registryUrl=http://localhost:8080/apis/registry/v3", + "apicurio-registry:" + path + "?registryUrl=http://localhost:8080/apis/registry/v3", ApicurioRegistryEndpoint.class); ApicurioRegistryConsumer consumer = (ApicurioRegistryConsumer) endpoint.createConsumer(exchange -> { }); assertThatThrownBy(consumer::poll).isInstanceOf(IllegalArgumentException.class) .hasMessage("Both groupId and artifactId are required for the consumer"); } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + void testPooledExchangeReleasedOnFailureAndRetry(boolean fetchContent) throws Exception { + context.getCamelContextExtension().setExchangeFactory(new PooledExchangeFactory()); + context.getCamelContextExtension().getExchangeFactory().setStatisticsEnabled(true); + context.getCamelContextExtension().getExchangeFactoryManager().setStatisticsEnabled(true); + context.start(); + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + ENDPOINT_URI + "&startScheduler=false&fetchContent=" + fetchContent, ApicurioRegistryEndpoint.class); + endpoint.setRegistryClient(mockClient); + + SearchedVersion first = new SearchedVersion(); + first.setGlobalId(1L); + first.setVersion("1"); + SearchedVersion second = new SearchedVersion(); + second.setGlobalId(2L); + second.setVersion("2"); + VersionSearchResults results = new VersionSearchResults(); + results.setVersions(List.of(second, first)); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().get()).thenReturn(results); + IllegalStateException failure = new IllegalStateException("failed first version"); + if (fetchContent) { + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("1").content().get()) + .thenThrow(failure).thenReturn(new ByteArrayInputStream(new byte[] { 1 })); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("2").content().get()) + .thenReturn(new ByteArrayInputStream(new byte[] { 2 })); + } + List delivered = new ArrayList<>(); + List attempted = new ArrayList<>(); + ApicurioRegistryConsumer consumer = (ApicurioRegistryConsumer) endpoint.createConsumer(exchange -> { + attempted.add(exchange); + if (!fetchContent && attempted.size() == 1) { + throw failure; + } + delivered.add(exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class)); + }); + consumer.start(); + try { + var statistics = context.getCamelContextExtension().getExchangeFactoryManager().getStatistics(); + assertThatThrownBy(consumer::poll).isSameAs(failure); + assertThat(statistics.getReleasedCounter()).isEqualTo(1); + assertThat(delivered).isEmpty(); + + assertThat(consumer.poll()).isEqualTo(2); + assertThat(delivered).containsExactly(1L, 2L); + assertThat(statistics.getCreatedCounter()).isEqualTo(1); + assertThat(statistics.getAcquiredCounter()).isEqualTo(2); + assertThat(statistics.getReleasedCounter()).isEqualTo(3); + assertThat(consumer.poll()).isZero(); + } finally { + consumer.stop(); + } + } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java index 3beb5c5f9c739..375ddf71399f7 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java @@ -280,8 +280,13 @@ void testTestCompatibilitySuccess() throws Exception { .versions().post(any(CreateVersion.class), any())) .thenReturn(mockVersion); - Object result = template.requestBody("direct:testCompatibility", "{\"test\":true}"); - assertThat(result).isEqualTo(true); + var result = template.request("direct:testCompatibility", exchange -> { + exchange.getIn().setBody("{\"test\":true}"); + exchange.getIn().setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS, "stale error"); + }); + assertThat(result.getException()).isNull(); + assertThat(result.getIn().getBody()).isEqualTo(true); + assertThat(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)).isNull(); } @Test @@ -294,8 +299,10 @@ void testTestCompatibilityFailure() throws Exception { .versions().post(any(CreateVersion.class), any())) .thenThrow(ruleViolation("incompatible")); - Object result = template.requestBody("direct:testCompatibility", "{\"bad\":true}"); - assertThat(result).isEqualTo(false); + var result = template.request("direct:testCompatibility", exchange -> exchange.getIn().setBody("{\"bad\":true}")); + assertThat(result.getException()).isNull(); + assertThat(result.getIn().getBody()).isEqualTo(false); + assertThat(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)).isEqualTo("incompatible"); } @Test diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java index bac634701b76c..675797fcb34ee 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java @@ -1564,7 +1564,8 @@ public String apicurioRegistryValidationResult() { return "CamelApicurioRegistryValidationResult"; } /** - * Validation error details. + * Rule violation details from validate or testCompatibility. Cleared + * before each check. * * The option is a: {@code String} type. * From 3f10557b4bfdf8f5a748a763e40e713b3cc5996b Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 16 Sep 2026 10:35:29 +0000 Subject: [PATCH 07/13] Regen --- .../src/main/java/org/apache/camel/util/ArtifactUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java index 4ca0e601d7d6e..8261b34c9909e 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java @@ -48,6 +48,7 @@ Map. entry("alibaba-ots", "camel-alibaba-ots"), Map. entry("alibaba-sls", "camel-alibaba-sls"), Map. entry("alibaba-sms", "camel-alibaba-sms"), Map. entry("amqp", "camel-amqp"), + Map. entry("apicurio-registry", "camel-apicurio-registry"), Map. entry("arangodb", "camel-arangodb"), Map. entry("as2", "camel-as2"), Map. entry("asterisk", "camel-asterisk"), From dc31f422cf278bdb257379dadcee06820f5438f7 Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Tue, 29 Sep 2026 12:36:18 +0200 Subject: [PATCH 08/13] CAMEL-24172: Retry failed versions, page version lists and share Vert.x The consumer no longer advances its watermark when the route error handler records a failure, version lists are paged instead of truncated at the registry default, and endpoints share one component-level Vert.x instance. testCompatibility also sets the result header. Tests move to camel-test-junit6 and the test infra is registered in camel-test-infra-all. Co-authored-by: GitHub Copilot --- .../catalog/components/apicurio-registry.json | 17 +- .../docs/apicurio-registry-component.adoc | 28 ++- .../camel/catalog/test-infra/metadata.json | 11 + components/camel-apicurio-registry/pom.xml | 2 +- .../ApicurioRegistryComponentConfigurer.java | 8 + .../apicurioregistry/apicurio-registry.json | 17 +- .../docs/apicurio-registry-component.adoc | 28 ++- .../ApicurioRegistryComponent.java | 41 ++++ .../ApicurioRegistryConstants.java | 2 +- .../ApicurioRegistryConsumer.java | 62 +++++- .../ApicurioRegistryEndpoint.java | 29 +-- .../ApicurioRegistryProducer.java | 38 +++- .../ApicurioRegistryComponentTest.java | 2 +- .../ApicurioRegistryConsumerTest.java | 194 ++++++++---------- .../ApicurioRegistryEndpointTest.java | 58 ++++-- .../ApicurioRegistryProducerTest.java | 27 ++- .../apicurioregistry/FakeVersionPages.java | 92 +++++++++ .../ApicurioRegistryTestSupport.java | 2 +- ...icurioRegistryComponentBuilderFactory.java | 18 ++ ...picurioRegistryEndpointBuilderFactory.java | 2 +- test-infra/camel-test-infra-all/pom.xml | 11 + .../resources/META-INF/metadata.json | 11 + 22 files changed, 489 insertions(+), 211 deletions(-) create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/FakeVersionPages.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json index 9e0dc79b3be7b..80e611d92d3dc 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json @@ -34,13 +34,14 @@ "operation": { "index": 7, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, "autowiredEnabled": { "index": 8, "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." }, "configuration": { "index": 9, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, - "authType": { "index": 10, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 11, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, - "clientSecret": { "index": 12, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, - "password": { "index": 13, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, - "scope": { "index": 14, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, - "tokenEndpoint": { "index": 15, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 16, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "vertx": { "index": 10, "kind": "property", "displayName": "Vertx", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.vertx.core.Vertx", "deprecated": false, "autowired": true, "secret": false, "description": "To use an existing Vert.x instance for the registry clients. If not set, the component creates one shared Vert.x instance on first use and closes it when the component stops." }, + "authType": { "index": 11, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 12, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 13, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 14, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 15, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "tokenEndpoint": { "index": 16, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 17, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } }, "headers": { "CamelApicurioRegistryOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_OPERATION" }, @@ -55,7 +56,7 @@ "CamelApicurioRegistryGlobalId": { "index": 9, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version global ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_GLOBAL_ID" }, "CamelApicurioRegistryContentId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, "CamelApicurioRegistryVersionState": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, - "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether validation passed", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, + "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the check passed. Set by validate and testCompatibility.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Rule violation details from validate or testCompatibility. Cleared before each check.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } }, "properties": { diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc index 3bf96841c9b75..415eacd91109c 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc @@ -65,7 +65,7 @@ the `CamelApicurioRegistryOperation` header: | `getArtifactContent` | Retrieve the content of an artifact version. | `getArtifactMetadata` | Retrieve artifact metadata. | `searchArtifacts` | Search for artifacts using filter headers. -| `listVersions` | List all versions of an artifact. +| `listVersions` | List all versions of an artifact, ordered by global ID. The component follows the registry's pagination and returns every page. | `createGroup` | Create a new group. | `testCompatibility` | Test schema compatibility (dry run). Returns `true`/`false` in the body. | `validate` | Validate the message body against the artifact's rules (dry run). Throws `ApicurioRegistryValidationException` on failure when `failOnValidation=true`. @@ -83,9 +83,9 @@ stored schema. Authentication, connectivity, and other non-rule failures propaga exceptions, rather than returning a negative validation result. Both operations set `CamelApicurioRegistryValidationErrors` to the rule violation details -when a check fails and clear any previous value before checking. `testCompatibility` returns -the boolean result in the body; `validate` preserves the body and sets -`CamelApicurioRegistryValidationResult`. +when a check fails and clear any previous value before checking. Both set +`CamelApicurioRegistryValidationResult`; `testCompatibility` also returns the boolean result +in the body, while `validate` preserves the body. == Consumer @@ -97,9 +97,15 @@ On first start, all existing versions of the artifact are emitted as exchanges. only deliver versions created after the consumer started. The watermark is held in memory and resets on route/context restart. -If fetching content or invoking the processor throws an exception, the consumer releases -the exchange and stops the current poll without advancing the watermark for that version. -The failed version remains eligible for the next poll. +The consumer requests versions newest first, one page at a time, and stops at the first +version it has already seen, so artifacts with many versions are handled without loading +every page on each poll. + +If fetching content fails, or the route fails to process a version (including failures +handled by the error handler without being rethrown, such as with the default error handler), +the consumer reports the error through its exception handler and stops the current poll +without advancing the watermark. The failed version and any newer ones are delivered again +on the next poll. When `fetchContent=true`, the consumer fetches the version content and sets it as a `byte[]` exchange body, closing the SDK response stream before processing the exchange. @@ -208,8 +214,12 @@ from("direct:validateSchema") == Authentication -The endpoint manages the Vert.x instance used by its SDK client and closes it when -the endpoint stops. A client supplied through `registryClient` remains caller-owned; +All endpoints of the component share a single Vert.x instance for their SDK clients. The +component creates it on first use and closes it when the component stops. To reuse an existing +Vert.x instance, set the `vertx` component option or bind a single `Vertx` bean in the registry; +the component does not close a Vert.x instance it did not create. + +A client supplied through `registryClient` remains caller-owned; the caller is responsible for its underlying HTTP resources. The component supports three authentication modes via the `authType` option: diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json index 834a1750bf196..190ef16ef2a8b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json @@ -592,6 +592,17 @@ "version" : "4.24.0-SNAPSHOT", "serviceVersion" : "camel", "uiSupported" : false +}, { + "service" : "org.apache.camel.test.infra.apicurio.registry.services.ApicurioRegistryInfraService", + "description" : "Apicurio Registry is an API/Schema registry", + "implementation" : "org.apache.camel.test.infra.apicurio.registry.services.ApicurioRegistryLocalContainerInfraService", + "alias" : [ "apicurio-registry" ], + "aliasImplementation" : [ ], + "groupId" : "org.apache.camel", + "artifactId" : "camel-test-infra-apicurio-registry", + "version" : "4.23.0-SNAPSHOT", + "serviceVersion" : "3.3.0", + "uiSupported" : false }, { "service" : "org.apache.camel.test.infra.postgres.services.PostgresInfraService", "description" : "PostgreSQL with pgvector extension for vector similarity search", diff --git a/components/camel-apicurio-registry/pom.xml b/components/camel-apicurio-registry/pom.xml index aaa2126aa7bb8..a7e9f7e566211 100644 --- a/components/camel-apicurio-registry/pom.xml +++ b/components/camel-apicurio-registry/pom.xml @@ -54,7 +54,7 @@ org.apache.camel - camel-test-junit5 + camel-test-junit6 test diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java index f994d7b5f094e..d580a0b0227f6 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java @@ -59,10 +59,16 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "tokenendpoint": case "tokenEndpoint": getOrCreateConfiguration(target).setTokenEndpoint(property(camelContext, java.lang.String.class, value)); return true; case "username": getOrCreateConfiguration(target).setUsername(property(camelContext, java.lang.String.class, value)); return true; + case "vertx": target.setVertx(property(camelContext, io.vertx.core.Vertx.class, value)); return true; default: return false; } } + @Override + public String[] getAutowiredNames() { + return new String[]{"vertx"}; + } + @Override public Class getOptionType(String name, boolean ignoreCase) { switch (ignoreCase ? name.toLowerCase() : name) { @@ -95,6 +101,7 @@ public Class getOptionType(String name, boolean ignoreCase) { case "tokenendpoint": case "tokenEndpoint": return java.lang.String.class; case "username": return java.lang.String.class; + case "vertx": return io.vertx.core.Vertx.class; default: return null; } } @@ -132,6 +139,7 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "tokenendpoint": case "tokenEndpoint": return getOrCreateConfiguration(target).getTokenEndpoint(); case "username": return getOrCreateConfiguration(target).getUsername(); + case "vertx": return target.getVertx(); default: return null; } } diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json index 9e0dc79b3be7b..80e611d92d3dc 100644 --- a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json @@ -34,13 +34,14 @@ "operation": { "index": 7, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, "autowiredEnabled": { "index": 8, "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." }, "configuration": { "index": 9, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, - "authType": { "index": 10, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 11, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, - "clientSecret": { "index": 12, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, - "password": { "index": 13, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, - "scope": { "index": 14, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, - "tokenEndpoint": { "index": 15, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 16, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "vertx": { "index": 10, "kind": "property", "displayName": "Vertx", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.vertx.core.Vertx", "deprecated": false, "autowired": true, "secret": false, "description": "To use an existing Vert.x instance for the registry clients. If not set, the component creates one shared Vert.x instance on first use and closes it when the component stops." }, + "authType": { "index": 11, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 12, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 13, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 14, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 15, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "tokenEndpoint": { "index": 16, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 17, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } }, "headers": { "CamelApicurioRegistryOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_OPERATION" }, @@ -55,7 +56,7 @@ "CamelApicurioRegistryGlobalId": { "index": 9, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version global ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_GLOBAL_ID" }, "CamelApicurioRegistryContentId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, "CamelApicurioRegistryVersionState": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, - "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether validation passed", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, + "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the check passed. Set by validate and testCompatibility.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Rule violation details from validate or testCompatibility. Cleared before each check.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } }, "properties": { diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc index 3bf96841c9b75..415eacd91109c 100644 --- a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -65,7 +65,7 @@ the `CamelApicurioRegistryOperation` header: | `getArtifactContent` | Retrieve the content of an artifact version. | `getArtifactMetadata` | Retrieve artifact metadata. | `searchArtifacts` | Search for artifacts using filter headers. -| `listVersions` | List all versions of an artifact. +| `listVersions` | List all versions of an artifact, ordered by global ID. The component follows the registry's pagination and returns every page. | `createGroup` | Create a new group. | `testCompatibility` | Test schema compatibility (dry run). Returns `true`/`false` in the body. | `validate` | Validate the message body against the artifact's rules (dry run). Throws `ApicurioRegistryValidationException` on failure when `failOnValidation=true`. @@ -83,9 +83,9 @@ stored schema. Authentication, connectivity, and other non-rule failures propaga exceptions, rather than returning a negative validation result. Both operations set `CamelApicurioRegistryValidationErrors` to the rule violation details -when a check fails and clear any previous value before checking. `testCompatibility` returns -the boolean result in the body; `validate` preserves the body and sets -`CamelApicurioRegistryValidationResult`. +when a check fails and clear any previous value before checking. Both set +`CamelApicurioRegistryValidationResult`; `testCompatibility` also returns the boolean result +in the body, while `validate` preserves the body. == Consumer @@ -97,9 +97,15 @@ On first start, all existing versions of the artifact are emitted as exchanges. only deliver versions created after the consumer started. The watermark is held in memory and resets on route/context restart. -If fetching content or invoking the processor throws an exception, the consumer releases -the exchange and stops the current poll without advancing the watermark for that version. -The failed version remains eligible for the next poll. +The consumer requests versions newest first, one page at a time, and stops at the first +version it has already seen, so artifacts with many versions are handled without loading +every page on each poll. + +If fetching content fails, or the route fails to process a version (including failures +handled by the error handler without being rethrown, such as with the default error handler), +the consumer reports the error through its exception handler and stops the current poll +without advancing the watermark. The failed version and any newer ones are delivered again +on the next poll. When `fetchContent=true`, the consumer fetches the version content and sets it as a `byte[]` exchange body, closing the SDK response stream before processing the exchange. @@ -208,8 +214,12 @@ from("direct:validateSchema") == Authentication -The endpoint manages the Vert.x instance used by its SDK client and closes it when -the endpoint stops. A client supplied through `registryClient` remains caller-owned; +All endpoints of the component share a single Vert.x instance for their SDK clients. The +component creates it on first use and closes it when the component stops. To reuse an existing +Vert.x instance, set the `vertx` component option or bind a single `Vertx` bean in the registry; +the component does not close a Vert.x instance it did not create. + +A client supplied through `registryClient` remains caller-owned; the caller is responsible for its underlying HTTP resources. The component supports three authentication modes via the `authType` option: diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java index cb13c9e825de0..e18f7f294faf1 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java @@ -17,7 +17,9 @@ package org.apache.camel.component.apicurioregistry; import java.util.Map; +import java.util.concurrent.TimeUnit; +import io.vertx.core.Vertx; import org.apache.camel.CamelContext; import org.apache.camel.Endpoint; import org.apache.camel.spi.Metadata; @@ -30,6 +32,13 @@ public class ApicurioRegistryComponent extends DefaultComponent { @Metadata(label = "advanced", description = "The component configuration") private ApicurioRegistryConfiguration configuration = new ApicurioRegistryConfiguration(); + @Metadata(label = "advanced", autowired = true, + description = "To use an existing Vert.x instance for the registry clients. If not set, the component creates" + + " one shared Vert.x instance on first use and closes it when the component stops.") + private Vertx vertx; + + private boolean managedVertx; + public ApicurioRegistryComponent() { } @@ -56,6 +65,38 @@ protected Endpoint createEndpoint(String uri, String remaining, Map versions = new ArrayList<>(results.getVersions()); - versions.sort(Comparator.comparingLong(SearchedVersion::getGlobalId)); + List versions = fetchNewVersions(client, groupId, artifactId); int count = 0; for (SearchedVersion version : versions) { @@ -93,6 +90,13 @@ protected int poll() throws Exception { } getProcessor().process(exchange); + if (exchange.getException() != null) { + // do not advance the watermark so the version is retried on the next poll + getExceptionHandler().handleException( + "Error processing artifact version with globalId " + globalId, exchange, + exchange.getException()); + break; + } lastSeenGlobalId = globalId; count++; } finally { @@ -102,4 +106,42 @@ protected int poll() throws Exception { } return count; } + + /** + * Fetches the versions newer than the watermark, in ascending globalId order. Pages are requested newest first so + * that polling stops as soon as an already seen version is reached. + */ + private List fetchNewVersions(RegistryClient client, String groupId, String artifactId) { + // keyed by globalId: a version created between page requests shifts the pages and may repeat one entry + Map answer = new TreeMap<>(); + int offset = 0; + while (true) { + final int pageOffset = offset; + VersionSearchResults page = client.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) + .versions().get(config -> { + config.queryParameters.orderby = VersionSortBy.GlobalId; + config.queryParameters.order = SortOrder.Desc; + config.queryParameters.offset = pageOffset; + config.queryParameters.limit = PAGE_SIZE; + }); + List versions = page != null ? page.getVersions() : null; + if (versions == null || versions.isEmpty()) { + break; + } + boolean reachedWatermark = false; + for (SearchedVersion version : versions) { + if (lastSeenGlobalId != null && version.getGlobalId() <= lastSeenGlobalId) { + reachedWatermark = true; + break; + } + answer.put(version.getGlobalId(), version); + } + offset += versions.size(); + if (reachedWatermark || versions.size() < PAGE_SIZE + || page.getCount() != null && offset >= page.getCount()) { + break; + } + } + return new ArrayList<>(answer.values()); + } } diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java index 49bf8b434cef6..7376567f5c79b 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java @@ -16,12 +16,9 @@ */ package org.apache.camel.component.apicurioregistry; -import java.util.concurrent.TimeUnit; - import io.apicurio.registry.client.RegistryClientFactory; import io.apicurio.registry.client.common.RegistryClientOptions; import io.apicurio.registry.rest.client.RegistryClient; -import io.vertx.core.Vertx; import org.apache.camel.Category; import org.apache.camel.Consumer; import org.apache.camel.Processor; @@ -52,7 +49,7 @@ public class ApicurioRegistryEndpoint extends ScheduledPollEndpoint implements E @UriParam(label = "advanced", description = "To use a pre-configured RegistryClient instance") private RegistryClient registryClient; - private Vertx vertx; + private boolean managedRegistryClient; ApicurioRegistryEndpoint(String uri, ApicurioRegistryComponent component, ApicurioRegistryConfiguration configuration, @@ -79,35 +76,23 @@ public Consumer createConsumer(Processor processor) throws Exception { protected void doStart() throws Exception { super.doStart(); if (registryClient == null) { - vertx = Vertx.vertx(); - try { - registryClient = createRegistryClient(); - } catch (Exception e) { - closeVertx(); - throw e; - } + registryClient = createRegistryClient(); + managedRegistryClient = true; } } @Override protected void doStop() throws Exception { super.doStop(); - if (vertx != null) { + if (managedRegistryClient) { registryClient = null; - closeVertx(); - } - } - - private void closeVertx() throws Exception { - try { - vertx.close().toCompletionStage().toCompletableFuture().get(30, TimeUnit.SECONDS); - } finally { - vertx = null; + managedRegistryClient = false; } } private RegistryClient createRegistryClient() { - RegistryClientOptions options = RegistryClientOptions.create(configuration.getRegistryUrl(), vertx); + RegistryClientOptions options = RegistryClientOptions.create(configuration.getRegistryUrl(), + ((ApicurioRegistryComponent) getComponent()).getOrCreateVertx()); String authType = configuration.getAuthType(); if ("basic".equalsIgnoreCase(authType)) { options.basicAuth(configuration.getUsername(), configuration.getPassword()); diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java index 3ce2fbfc77f03..48784df14708a 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java @@ -17,6 +17,8 @@ package org.apache.camel.component.apicurioregistry; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactMetaData; @@ -27,15 +29,20 @@ import io.apicurio.registry.rest.client.models.GroupMetaData; import io.apicurio.registry.rest.client.models.IfArtifactExists; import io.apicurio.registry.rest.client.models.RuleViolationProblemDetails; +import io.apicurio.registry.rest.client.models.SearchedVersion; +import io.apicurio.registry.rest.client.models.SortOrder; import io.apicurio.registry.rest.client.models.VersionContent; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; +import io.apicurio.registry.rest.client.models.VersionSortBy; import org.apache.camel.Message; import org.apache.camel.spi.InvokeOnHeader; import org.apache.camel.support.HeaderSelectorProducer; public class ApicurioRegistryProducer extends HeaderSelectorProducer { + private static final int PAGE_SIZE = 100; + private final ApicurioRegistryEndpoint endpoint; private final ApicurioRegistryConfiguration configuration; @@ -174,8 +181,34 @@ public void listVersions(Message message) { String groupId = resolveGroupId(message); String artifactId = resolveArtifactId(message); - VersionSearchResults results = getClient().groups().byGroupId(groupId).artifacts() - .byArtifactId(artifactId).versions().get(); + List all = new ArrayList<>(); + Integer total = null; + int offset = 0; + while (true) { + final int pageOffset = offset; + VersionSearchResults page = getClient().groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).versions().get(config -> { + config.queryParameters.orderby = VersionSortBy.GlobalId; + config.queryParameters.order = SortOrder.Asc; + config.queryParameters.offset = pageOffset; + config.queryParameters.limit = PAGE_SIZE; + }); + List versions = page != null ? page.getVersions() : null; + if (page != null && page.getCount() != null) { + total = page.getCount(); + } + if (versions == null || versions.isEmpty()) { + break; + } + all.addAll(versions); + offset += versions.size(); + if (versions.size() < PAGE_SIZE || total != null && offset >= total) { + break; + } + } + VersionSearchResults results = new VersionSearchResults(); + results.setVersions(all); + results.setCount(total != null ? total : all.size()); message.setBody(results); } @@ -196,6 +229,7 @@ public void createGroup(Message message) { @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_TEST_COMPATIBILITY) public void testCompatibility(Message message) throws Exception { boolean compatible = doDryRun(message); + message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, compatible); message.setBody(compatible); } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java index b6104f4a00ede..6145c7506da7a 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentTest.java @@ -16,7 +16,7 @@ */ package org.apache.camel.component.apicurioregistry; -import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.camel.test.junit6.CamelTestSupport; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java index c4b7c8ee37640..6f8aa98d6bffc 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -21,10 +21,10 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.LongStream; import io.apicurio.registry.rest.client.RegistryClient; -import io.apicurio.registry.rest.client.models.SearchedVersion; -import io.apicurio.registry.rest.client.models.VersionSearchResults; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.builder.RouteBuilder; @@ -48,10 +48,11 @@ class ApicurioRegistryConsumerTest { private static final String ENDPOINT_URI - = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=500"; + = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&delay=100"; private final RegistryClient mockClient = mock(RegistryClient.class, RETURNS_DEEP_STUBS); private CamelContext context; + private FakeVersionPages pages; @BeforeEach void setUp() { @@ -59,6 +60,8 @@ void setUp() { ApicurioRegistryComponent component = new ApicurioRegistryComponent(context); component.getConfiguration().setRegistryUrl("http://localhost:8080/apis/registry/v3"); context.addComponent("apicurio-registry", component); + pages = new FakeVersionPages( + mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact").versions()); } @AfterEach @@ -68,134 +71,91 @@ void tearDown() throws Exception { } } - private void setupAndStartRoute(VersionSearchResults results) throws Exception { - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().get()) - .thenReturn(results); - - ApicurioRegistryEndpoint endpoint = (ApicurioRegistryEndpoint) context.getEndpoint(ENDPOINT_URI); + private ApicurioRegistryConsumer createConsumer(String uri, List received) throws Exception { + ApicurioRegistryEndpoint endpoint = context.getEndpoint(uri, ApicurioRegistryEndpoint.class); endpoint.setRegistryClient(mockClient); + return (ApicurioRegistryConsumer) endpoint.createConsumer(received::add); + } + + private static List globalIds(List exchanges) { + return exchanges.stream() + .map(e -> e.getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class)) + .toList(); + } + @Test + void testPollNewVersionsInRoute() throws Exception { + pages.add(1, 2); + ApicurioRegistryEndpoint endpoint = context.getEndpoint(ENDPOINT_URI, ApicurioRegistryEndpoint.class); + endpoint.setRegistryClient(mockClient); context.addRoutes(new RouteBuilder() { @Override public void configure() { from(ENDPOINT_URI).to("mock:result"); } }); - context.start(); - } - - @Test - void testPollNewVersions() throws Exception { - SearchedVersion v1 = new SearchedVersion(); - v1.setGlobalId(1L); - v1.setVersion("1.0"); - v1.setContentId(100L); - v1.setArtifactType("JSON"); - - SearchedVersion v2 = new SearchedVersion(); - v2.setGlobalId(2L); - v2.setVersion("2.0"); - v2.setContentId(101L); - v2.setArtifactType("JSON"); - - VersionSearchResults results = new VersionSearchResults(); - results.setVersions(List.of(v1, v2)); - - setupAndStartRoute(results); MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class); - mock.expectedMinimumMessageCount(2); + mock.expectedHeaderValuesReceivedInAnyOrder(ApicurioRegistryConstants.HEADER_GLOBAL_ID, 1L, 2L); MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); } @Test void testPollOutOfOrderGlobalIds() throws Exception { - SearchedVersion v1 = new SearchedVersion(); - v1.setGlobalId(5L); - v1.setVersion("1.0"); - v1.setContentId(100L); - v1.setArtifactType("JSON"); - - SearchedVersion v2 = new SearchedVersion(); - v2.setGlobalId(2L); - v2.setVersion("0.1"); - v2.setContentId(99L); - v2.setArtifactType("JSON"); - - SearchedVersion v3 = new SearchedVersion(); - v3.setGlobalId(8L); - v3.setVersion("2.0"); - v3.setContentId(102L); - v3.setArtifactType("JSON"); - - VersionSearchResults results = new VersionSearchResults(); - results.setVersions(List.of(v1, v2, v3)); - - setupAndStartRoute(results); + pages.add(5, 2, 8); + List received = new ArrayList<>(); + ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI, received); - MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class); - mock.expectedMinimumMessageCount(3); - MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); - - List receivedIds = mock.getReceivedExchanges().stream() - .map(e -> e.getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class)) - .toList(); - assertThat(receivedIds).containsExactly(2L, 5L, 8L); + assertThat(consumer.poll()).isEqualTo(3); + assertThat(globalIds(received)).containsExactly(2L, 5L, 8L); } @Test - void testPollNoNewVersionsAfterInitial() throws Exception { - SearchedVersion v1 = new SearchedVersion(); - v1.setGlobalId(1L); - v1.setVersion("1.0"); - v1.setContentId(100L); - v1.setArtifactType("JSON"); - - VersionSearchResults results = new VersionSearchResults(); - results.setVersions(List.of(v1)); - - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().get()).thenReturn(results); - ApicurioRegistryEndpoint endpoint = context.getEndpoint(ENDPOINT_URI, ApicurioRegistryEndpoint.class); - endpoint.setRegistryClient(mockClient); - List exchanges = new ArrayList<>(); - ApicurioRegistryConsumer consumer = (ApicurioRegistryConsumer) endpoint.createConsumer(exchanges::add); + void testPollOnlyNewVersionsAfterInitial() throws Exception { + pages.add(1); + List received = new ArrayList<>(); + ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI, received); assertThat(consumer.poll()).isEqualTo(1); assertThat(consumer.poll()).isZero(); - SearchedVersion v2 = new SearchedVersion(); - v2.setGlobalId(2L); - v2.setVersion("2.0"); - results.setVersions(List.of(v2, v1)); + pages.add(2); assertThat(consumer.poll()).isEqualTo(1); assertThat(consumer.poll()).isZero(); - assertThat(exchanges).hasSize(2); - assertThat(exchanges.get(1).getIn().getBody()).isSameAs(v2); + assertThat(globalIds(received)).containsExactly(1L, 2L); + } + + @Test + void testPollPagesThroughManyVersionsAndStopsAtWatermark() throws Exception { + pages.addRange(1, 250); + List received = new ArrayList<>(); + ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI, received); + + // more than the registry's default page of 20 versions, and more than one consumer page + assertThat(consumer.poll()).isEqualTo(250); + assertThat(globalIds(received)).containsExactlyElementsOf(LongStream.rangeClosed(1, 250).boxed().toList()); + assertThat(pages.requestedOffsets).containsExactly(0, 100, 200); + + pages.requestedOffsets.clear(); + pages.add(251); + assertThat(consumer.poll()).isEqualTo(1); + assertThat(globalIds(received)).endsWith(251L); + // the first page reaches the watermark, so older pages are not fetched again + assertThat(pages.requestedOffsets).containsExactly(0); } @Test void testFetchContentClosesStream() throws Exception { - ApicurioRegistryEndpoint endpoint = context.getEndpoint(ENDPOINT_URI + "&fetchContent=true", - ApicurioRegistryEndpoint.class); - endpoint.setRegistryClient(mockClient); - SearchedVersion version = new SearchedVersion(); - version.setGlobalId(1L); - version.setVersion("1"); - VersionSearchResults results = new VersionSearchResults(); - results.setVersions(List.of(version)); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().get()).thenReturn(results); + pages.add(1); byte[] content = "{\"type\":\"object\"}".getBytes(StandardCharsets.UTF_8); ByteArrayInputStream stream = spy(new ByteArrayInputStream(content)); when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") .versions().byVersionExpression("1").content().get()).thenReturn(stream); - List exchanges = new ArrayList<>(); - ApicurioRegistryConsumer consumer = (ApicurioRegistryConsumer) endpoint.createConsumer(exchanges::add); + List received = new ArrayList<>(); + ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI + "&fetchContent=true", received); assertThat(consumer.poll()).isEqualTo(1); - assertThat(exchanges.get(0).getIn().getBody()).isEqualTo(content); + assertThat(received.get(0).getIn().getBody()).isEqualTo(content); verify(stream).close(); } @@ -211,6 +171,37 @@ void testMissingIdentifier(String path) throws Exception { .hasMessage("Both groupId and artifactId are required for the consumer"); } + @Test + void testRouteFailureHandledByErrorHandlerIsRetried() throws Exception { + pages.add(1, 2); + AtomicInteger attemptsForFirstVersion = new AtomicInteger(); + ApicurioRegistryEndpoint endpoint = context.getEndpoint(ENDPOINT_URI, ApicurioRegistryEndpoint.class); + endpoint.setRegistryClient(mockClient); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + // the default error handler stores the failure on the exchange instead of rethrowing it + from(ENDPOINT_URI) + .process(exchange -> { + long globalId = exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class); + if (globalId == 1L && attemptsForFirstVersion.incrementAndGet() == 1) { + throw new IllegalStateException("simulated failure"); + } + }) + .to("mock:result"); + } + }); + context.start(); + + MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class); + mock.expectedHeaderValuesReceivedInAnyOrder(ApicurioRegistryConstants.HEADER_GLOBAL_ID, 1L, 2L); + MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); + + // version 2 is only delivered after the failed version 1 was retried, in order + assertThat(globalIds(mock.getReceivedExchanges())).containsExactly(1L, 2L); + assertThat(attemptsForFirstVersion.get()).isEqualTo(2); + } + @ParameterizedTest @ValueSource(booleans = { false, true }) void testPooledExchangeReleasedOnFailureAndRetry(boolean fetchContent) throws Exception { @@ -221,17 +212,8 @@ void testPooledExchangeReleasedOnFailureAndRetry(boolean fetchContent) throws Ex ApicurioRegistryEndpoint endpoint = context.getEndpoint( ENDPOINT_URI + "&startScheduler=false&fetchContent=" + fetchContent, ApicurioRegistryEndpoint.class); endpoint.setRegistryClient(mockClient); + pages.add(2, 1); - SearchedVersion first = new SearchedVersion(); - first.setGlobalId(1L); - first.setVersion("1"); - SearchedVersion second = new SearchedVersion(); - second.setGlobalId(2L); - second.setVersion("2"); - VersionSearchResults results = new VersionSearchResults(); - results.setVersions(List.of(second, first)); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().get()).thenReturn(results); IllegalStateException failure = new IllegalStateException("failed first version"); if (fetchContent) { when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java index 2197a6ba7fd2c..e97427851f30d 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java @@ -18,6 +18,8 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import com.sun.net.httpserver.HttpServer; @@ -31,10 +33,10 @@ import org.mockito.MockedStatic; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -101,45 +103,59 @@ private void testAuthentication(String options, String authorization, boolean oi } @Test - void testOwnedResourcesClosedOnStopAndRestart() throws Exception { + void testEndpointsShareComponentVertxClosedWithComponent() throws Exception { Vertx vertx = mock(Vertx.class); when(vertx.close()).thenReturn(Future.succeededFuture()); - RegistryClient client = mock(RegistryClient.class); + List clientVertx = new ArrayList<>(); try (MockedStatic vertxFactory = mockStatic(Vertx.class); MockedStatic clientFactory = mockStatic(RegistryClientFactory.class); DefaultCamelContext context = new DefaultCamelContext()) { vertxFactory.when(Vertx::vertx).thenReturn(vertx); clientFactory.when(() -> RegistryClientFactory.create(any())).thenAnswer(call -> { - assertThat(call.getArgument(0, RegistryClientOptions.class).getVertx()) - .isSameAs(vertx); - return client; + clientVertx.add(call.getArgument(0, RegistryClientOptions.class).getVertx()); + return mock(RegistryClient.class); }); - ApicurioRegistryEndpoint endpoint = context.getEndpoint( + context.start(); + ApicurioRegistryEndpoint first = context.getEndpoint( "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3", ApicurioRegistryEndpoint.class); - endpoint.start(); - assertThat(endpoint.getRegistryClient()).isSameAs(client); - endpoint.stop(); - assertThat(endpoint.getRegistryClient()).isNull(); - endpoint.start(); - endpoint.stop(); - verify(vertx, times(2)).close(); + ApicurioRegistryEndpoint second = context.getEndpoint( + "apicurio-registry:g/b?registryUrl=http://localhost:8080/apis/registry/v3", ApicurioRegistryEndpoint.class); + first.start(); + second.start(); + assertThat(clientVertx).containsExactly(vertx, vertx); + vertxFactory.verify(Vertx::vertx, times(1)); + + // stopping and restarting an endpoint recreates its client but keeps the shared Vert.x instance + first.stop(); + assertThat(first.getRegistryClient()).isNull(); + first.start(); + assertThat(first.getRegistryClient()).isNotNull(); + verify(vertx, never()).close(); + vertxFactory.verify(Vertx::vertx, times(1)); + + context.getComponent("apicurio-registry").stop(); + verify(vertx).close(); } } @Test - void testFailedClientCreationClosesResources() throws Exception { + void testProvidedVertxIsNotClosed() throws Exception { Vertx vertx = mock(Vertx.class); - when(vertx.close()).thenReturn(Future.succeededFuture()); try (MockedStatic vertxFactory = mockStatic(Vertx.class); MockedStatic clientFactory = mockStatic(RegistryClientFactory.class); DefaultCamelContext context = new DefaultCamelContext()) { - vertxFactory.when(Vertx::vertx).thenReturn(vertx); - clientFactory.when(() -> RegistryClientFactory.create(any())) - .thenThrow(new IllegalArgumentException("invalid client")); + clientFactory.when(() -> RegistryClientFactory.create(any())).thenAnswer(call -> { + assertThat(call.getArgument(0, RegistryClientOptions.class).getVertx()).isSameAs(vertx); + return mock(RegistryClient.class); + }); + context.getComponent("apicurio-registry", ApicurioRegistryComponent.class).setVertx(vertx); + context.start(); ApicurioRegistryEndpoint endpoint = context.getEndpoint( "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3", ApicurioRegistryEndpoint.class); - assertThatThrownBy(endpoint::start).isInstanceOf(IllegalArgumentException.class).hasMessage("invalid client"); - verify(vertx).close(); + endpoint.start(); + context.stop(); + vertxFactory.verify(Vertx::vertx, never()); + verify(vertx, never()).close(); } } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java index 375ddf71399f7..c9e6f7b34531b 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.stream.LongStream; import com.microsoft.kiota.ApiException; import io.apicurio.registry.rest.client.RegistryClient; @@ -35,13 +36,14 @@ import io.apicurio.registry.rest.client.models.IfArtifactExists; import io.apicurio.registry.rest.client.models.ProblemDetails; import io.apicurio.registry.rest.client.models.RuleViolationProblemDetails; +import io.apicurio.registry.rest.client.models.SearchedVersion; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; import org.apache.camel.BindToRegistry; import org.apache.camel.CamelExecutionException; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.camel.test.junit6.CamelTestSupport; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -198,19 +200,20 @@ void testGetArtifactContent() throws Exception { } @Test - void testListVersions() throws Exception { + void testListVersionsReturnsEveryPage() throws Exception { String endpointUri = "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=listVersions"; injectMockClient(endpointUri); - - VersionSearchResults mockResults = new VersionSearchResults(); - mockResults.setVersions(List.of()); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().get()) - .thenReturn(mockResults); - - Object result = template.requestBody("direct:listVersions", (Object) null); - assertThat(result).isSameAs(mockResults); + FakeVersionPages pages = new FakeVersionPages( + mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact").versions()) + .addRange(1, 250); + + VersionSearchResults result + = template.requestBody("direct:listVersions", (Object) null, VersionSearchResults.class); + assertThat(result.getCount()).isEqualTo(250); + assertThat(result.getVersions()).extracting(SearchedVersion::getGlobalId) + .containsExactlyElementsOf(LongStream.rangeClosed(1, 250).boxed().toList()); + assertThat(pages.requestedOffsets).containsExactly(0, 100, 200); } @Test @@ -286,6 +289,7 @@ void testTestCompatibilitySuccess() throws Exception { }); assertThat(result.getException()).isNull(); assertThat(result.getIn().getBody()).isEqualTo(true); + assertThat(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, Boolean.class)).isTrue(); assertThat(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)).isNull(); } @@ -302,6 +306,7 @@ void testTestCompatibilityFailure() throws Exception { var result = template.request("direct:testCompatibility", exchange -> exchange.getIn().setBody("{\"bad\":true}")); assertThat(result.getException()).isNull(); assertThat(result.getIn().getBody()).isEqualTo(false); + assertThat(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, Boolean.class)).isFalse(); assertThat(result.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS)).isEqualTo("incompatible"); } diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/FakeVersionPages.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/FakeVersionPages.java new file mode 100644 index 0000000000000..ce3e48d509433 --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/FakeVersionPages.java @@ -0,0 +1,92 @@ +/* + * 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.apicurioregistry; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Consumer; + +import io.apicurio.registry.rest.client.groups.item.artifacts.item.versions.VersionsRequestBuilder; +import io.apicurio.registry.rest.client.models.SearchedVersion; +import io.apicurio.registry.rest.client.models.SortOrder; +import io.apicurio.registry.rest.client.models.VersionSearchResults; +import io.apicurio.registry.rest.client.models.VersionSortBy; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * Stubs a mocked versions endpoint so that it behaves like the registry: it honours the orderby, order, offset and + * limit query parameters and applies a default page size of 20 when no limit is given. + */ +final class FakeVersionPages { + + static final int REGISTRY_DEFAULT_LIMIT = 20; + + final List versions = new ArrayList<>(); + final List requestedOffsets = new ArrayList<>(); + + @SuppressWarnings("unchecked") + FakeVersionPages(VersionsRequestBuilder builder) { + when(builder.get()).thenAnswer(call -> page(builder, null)); + when(builder.get(any(Consumer.class))).thenAnswer( + call -> page(builder, (Consumer) call.getArgument(0))); + } + + static SearchedVersion version(long globalId) { + SearchedVersion version = new SearchedVersion(); + version.setGlobalId(globalId); + version.setVersion(String.valueOf(globalId)); + version.setArtifactType("JSON"); + return version; + } + + FakeVersionPages add(long... globalIds) { + for (long globalId : globalIds) { + versions.add(version(globalId)); + } + return this; + } + + FakeVersionPages addRange(long fromInclusive, long toInclusive) { + for (long id = fromInclusive; id <= toInclusive; id++) { + versions.add(version(id)); + } + return this; + } + + private VersionSearchResults page( + VersionsRequestBuilder builder, Consumer configurer) { + VersionsRequestBuilder.GetRequestConfiguration config = builder.new GetRequestConfiguration(); + if (configurer != null) { + configurer.accept(config); + } + List sorted = new ArrayList<>(versions); + if (config.queryParameters.orderby == VersionSortBy.GlobalId) { + Comparator byGlobalId = Comparator.comparingLong(SearchedVersion::getGlobalId); + sorted.sort(config.queryParameters.order == SortOrder.Desc ? byGlobalId.reversed() : byGlobalId); + } + int offset = config.queryParameters.offset != null ? config.queryParameters.offset : 0; + int limit = config.queryParameters.limit != null ? config.queryParameters.limit : REGISTRY_DEFAULT_LIMIT; + requestedOffsets.add(offset); + VersionSearchResults results = new VersionSearchResults(); + results.setCount(sorted.size()); + results.setVersions(sorted.subList(Math.min(offset, sorted.size()), Math.min(offset + limit, sorted.size()))); + return results; + } +} diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryTestSupport.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryTestSupport.java index f79a08f63f50c..9ae3540c81ccd 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryTestSupport.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryTestSupport.java @@ -20,7 +20,7 @@ import org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent; import org.apache.camel.test.infra.apicurio.registry.services.ApicurioRegistryService; import org.apache.camel.test.infra.apicurio.registry.services.ApicurioRegistryServiceFactory; -import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.camel.test.junit6.CamelTestSupport; import org.junit.jupiter.api.extension.RegisterExtension; public class ApicurioRegistryTestSupport extends CamelTestSupport { diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java index 1282b2ea7e34b..1c59cddcf2e5a 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java @@ -241,6 +241,23 @@ default ApicurioRegistryComponentBuilder configuration(org.apache.camel.componen return this; } + /** + * To use an existing Vert.x instance for the registry clients. If not + * set, the component creates one shared Vert.x instance on first use + * and closes it when the component stops. + * + * The option is a: <code>io.vertx.core.Vertx</code> type. + * + * Group: advanced + * + * @param vertx the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder vertx(io.vertx.core.Vertx vertx) { + doSetProperty("vertx", vertx); + return this; + } + /** * The authentication type to use. @@ -378,6 +395,7 @@ protected boolean setPropertyOnComponent( case "operation": getOrCreateConfiguration((ApicurioRegistryComponent) component).setOperation((java.lang.String) value); return true; case "autowiredEnabled": ((ApicurioRegistryComponent) component).setAutowiredEnabled((boolean) value); return true; case "configuration": ((ApicurioRegistryComponent) component).setConfiguration((org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration) value); return true; + case "vertx": ((ApicurioRegistryComponent) component).setVertx((io.vertx.core.Vertx) value); return true; case "authType": getOrCreateConfiguration((ApicurioRegistryComponent) component).setAuthType((java.lang.String) value); return true; case "clientId": getOrCreateConfiguration((ApicurioRegistryComponent) component).setClientId((java.lang.String) value); return true; case "clientSecret": getOrCreateConfiguration((ApicurioRegistryComponent) component).setClientSecret((java.lang.String) value); return true; diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java index 675797fcb34ee..1662c75f196cc 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java @@ -1551,7 +1551,7 @@ public String apicurioRegistryVersionState() { return "CamelApicurioRegistryVersionState"; } /** - * Whether validation passed. + * Whether the check passed. Set by validate and testCompatibility. * * The option is a: {@code Boolean} type. * diff --git a/test-infra/camel-test-infra-all/pom.xml b/test-infra/camel-test-infra-all/pom.xml index 4d7dc5003b4f1..548404b3fb891 100644 --- a/test-infra/camel-test-infra-all/pom.xml +++ b/test-infra/camel-test-infra-all/pom.xml @@ -96,6 +96,11 @@ camel-test-infra-dispatch-router ${project.version} + + org.apache.camel + camel-test-infra-apicurio-registry + ${project.version} + org.apache.camel camel-test-infra-arangodb @@ -353,6 +358,12 @@ prepare-package + + + org.apache.camel + camel-test-infra-apicurio-registry + + org.apache.camel diff --git a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json index 834a1750bf196..190ef16ef2a8b 100644 --- a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json +++ b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json @@ -592,6 +592,17 @@ "version" : "4.24.0-SNAPSHOT", "serviceVersion" : "camel", "uiSupported" : false +}, { + "service" : "org.apache.camel.test.infra.apicurio.registry.services.ApicurioRegistryInfraService", + "description" : "Apicurio Registry is an API/Schema registry", + "implementation" : "org.apache.camel.test.infra.apicurio.registry.services.ApicurioRegistryLocalContainerInfraService", + "alias" : [ "apicurio-registry" ], + "aliasImplementation" : [ ], + "groupId" : "org.apache.camel", + "artifactId" : "camel-test-infra-apicurio-registry", + "version" : "4.23.0-SNAPSHOT", + "serviceVersion" : "3.3.0", + "uiSupported" : false }, { "service" : "org.apache.camel.test.infra.postgres.services.PostgresInfraService", "description" : "PostgreSQL with pgvector extension for vector similarity search", From 954c45095c488ef9ae941cc89ec8ead521e65de3 Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Wed, 7 Oct 2026 09:18:46 +0200 Subject: [PATCH 09/13] CAMEL-24172: Close web clients, skip unreadable versions and page searches Endpoints now own and close their Vert.x WebClient (and OAuth2 provider), with TLS and proxy options. setVertx no longer lets the component close a user instance. The consumer skips disabled or missing versions when fetching content, probes before paging, fails fast on missing IDs and avoids logging failures twice. searchArtifacts applies the artifact ID and other filters and exposes paging and sorting headers. Producer operations report missing IDs clearly, and the docs cover deletion, consumer headers and retry behaviour. Co-authored-by: GitHub Copilot --- .../catalog/components/apicurio-registry.json | 73 ++-- .../docs/apicurio-registry-component.adoc | 66 ++- components/camel-apicurio-registry/pom.xml | 5 + .../ApicurioRegistryComponentConfigurer.java | 30 ++ .../ApicurioRegistryEndpointConfigurer.java | 30 ++ .../ApicurioRegistryEndpointUriFactory.java | 10 +- .../apicurioregistry/apicurio-registry.json | 73 ++-- .../docs/apicurio-registry-component.adoc | 66 ++- .../ApicurioRegistryComponent.java | 25 +- .../ApicurioRegistryConfiguration.java | 63 ++- .../ApicurioRegistryConstants.java | 24 ++ .../ApicurioRegistryConsumer.java | 117 ++++-- .../ApicurioRegistryEndpoint.java | 93 ++++- .../ApicurioRegistryProducer.java | 116 ++++-- .../ApicurioRegistryConsumerTest.java | 96 ++++- .../ApicurioRegistryEndpointTest.java | 80 +++- .../ApicurioRegistryProducerTest.java | 68 ++++ .../apicurioregistry/FakeVersionPages.java | 7 + .../ApicurioRegistryProducerIT.java | 87 ++-- .../src/test/resources/truststore.p12 | Bin 0 -> 1094 bytes ...icurioRegistryComponentBuilderFactory.java | 83 ++++ ...picurioRegistryEndpointBuilderFactory.java | 378 ++++++++++++++++++ 22 files changed, 1383 insertions(+), 207 deletions(-) create mode 100644 components/camel-apicurio-registry/src/test/resources/truststore.p12 diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json index 80e611d92d3dc..9b2684a33b96f 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json @@ -35,13 +35,18 @@ "autowiredEnabled": { "index": 8, "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." }, "configuration": { "index": 9, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, "vertx": { "index": 10, "kind": "property", "displayName": "Vertx", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.vertx.core.Vertx", "deprecated": false, "autowired": true, "secret": false, "description": "To use an existing Vert.x instance for the registry clients. If not set, the component creates one shared Vert.x instance on first use and closes it when the component stops." }, - "authType": { "index": 11, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 12, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, - "clientSecret": { "index": 13, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, - "password": { "index": 14, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, - "scope": { "index": 15, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, - "tokenEndpoint": { "index": 16, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 17, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "proxyHost": { "index": 11, "kind": "property", "displayName": "Proxy Host", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The proxy host to use for registry (and OAuth2 token) requests" }, + "proxyPassword": { "index": 12, "kind": "property", "displayName": "Proxy Password", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The password for proxy authentication" }, + "proxyPort": { "index": 13, "kind": "property", "displayName": "Proxy Port", "group": "proxy", "label": "proxy", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The proxy port" }, + "proxyUsername": { "index": 14, "kind": "property", "displayName": "Proxy Username", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The username for proxy authentication" }, + "authType": { "index": 15, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 16, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 17, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 18, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 19, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "sslContextParameters": { "index": 20, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "To configure TLS, such as a trust store for a registry with a private CA or a client certificate (mTLS), using the Camel JSSE configuration. Also applies to the OAuth2 token endpoint." }, + "tokenEndpoint": { "index": 21, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 22, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } }, "headers": { "CamelApicurioRegistryOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_OPERATION" }, @@ -57,7 +62,12 @@ "CamelApicurioRegistryContentId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, "CamelApicurioRegistryVersionState": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the check passed. Set by validate and testCompatibility.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, - "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Rule violation details from validate or testCompatibility. Cleared before each check.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } + "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Rule violation details from validate or testCompatibility. Cleared before each check.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" }, + "CamelApicurioRegistryLabels": { "index": 14, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Labels to filter searchArtifacts by, as key:value pairs. Either a comma-separated String or a collection.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_LABELS" }, + "CamelApicurioRegistrySearchOffset": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of results to skip in searchArtifacts. Defaults to 0.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_SEARCH_OFFSET" }, + "CamelApicurioRegistrySearchLimit": { "index": 16, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The maximum number of results returned by searchArtifacts. The registry defaults to 20.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_SEARCH_LIMIT" }, + "CamelApicurioRegistrySearchOrder": { "index": 17, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The sort order for searchArtifacts: asc or desc", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_SEARCH_ORDER" }, + "CamelApicurioRegistrySearchOrderBy": { "index": 18, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The field to sort searchArtifacts by: groupId, artifactId, createdOn, modifiedOn, artifactType or name", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_SEARCH_ORDER_BY" } }, "properties": { "groupId": { "index": 0, "kind": "path", "displayName": "Group Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact group ID" }, @@ -75,26 +85,31 @@ "operation": { "index": 12, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, "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." }, "registryClient": { "index": 14, "kind": "parameter", "displayName": "Registry Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.apicurio.registry.rest.client.RegistryClient", "deprecated": false, "autowired": false, "secret": false, "description": "To use a pre-configured RegistryClient instance" }, - "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." }, - "authType": { "index": 29, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 30, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, - "clientSecret": { "index": 31, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, - "password": { "index": 32, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, - "scope": { "index": 33, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, - "tokenEndpoint": { "index": 34, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 35, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "proxyHost": { "index": 15, "kind": "parameter", "displayName": "Proxy Host", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The proxy host to use for registry (and OAuth2 token) requests" }, + "proxyPassword": { "index": 16, "kind": "parameter", "displayName": "Proxy Password", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The password for proxy authentication" }, + "proxyPort": { "index": 17, "kind": "parameter", "displayName": "Proxy Port", "group": "proxy", "label": "proxy", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The proxy port" }, + "proxyUsername": { "index": 18, "kind": "parameter", "displayName": "Proxy Username", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The username for proxy authentication" }, + "backoffErrorThreshold": { "index": 19, "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": 20, "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": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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": 26, "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": 27, "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": 28, "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": 29, "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": 30, "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": 31, "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": 32, "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." }, + "authType": { "index": 33, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 34, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 35, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 36, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 37, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "sslContextParameters": { "index": 38, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "To configure TLS, such as a trust store for a registry with a private CA or a client certificate (mTLS), using the Camel JSSE configuration. Also applies to the OAuth2 token endpoint." }, + "tokenEndpoint": { "index": 39, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 40, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc index 415eacd91109c..0eb34f17436d6 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc @@ -61,10 +61,10 @@ the `CamelApicurioRegistryOperation` header: | `createArtifact` | Create a new artifact. Body contains the artifact content. | `updateArtifact` | Create a new version of an existing artifact. Body contains the new content. -| `deleteArtifact` | Delete an artifact. +| `deleteArtifact` | Delete an artifact. Apicurio Registry 3.x disables artifact deletion by default; the registry must run with `apicurio.rest.deletion.artifact.enabled=true` (environment variable `APICURIO_REST_DELETION_ARTIFACT_ENABLED=true`), otherwise the operation fails with HTTP 405. | `getArtifactContent` | Retrieve the content of an artifact version. | `getArtifactMetadata` | Retrieve artifact metadata. -| `searchArtifacts` | Search for artifacts using filter headers. +| `searchArtifacts` | Search for artifacts. See the "Searching artifacts" section below. | `listVersions` | List all versions of an artifact, ordered by global ID. The component follows the registry's pagination and returns every page. | `createGroup` | Create a new group. | `testCompatibility` | Test schema compatibility (dry run). Returns `true`/`false` in the body. @@ -87,6 +87,35 @@ when a check fails and clear any previous value before checking. Both set `CamelApicurioRegistryValidationResult`; `testCompatibility` also returns the boolean result in the body, while `validate` preserves the body. +Operations that act on a specific group or artifact fail with an `IllegalArgumentException` +when the group or artifact ID is set neither in the endpoint path nor in a header. + +=== Searching artifacts + +`searchArtifacts` filters by group ID and artifact ID (endpoint path or headers), and by the +`CamelApicurioRegistryArtifactName`, `CamelApicurioRegistryArtifactDescription`, +`CamelApicurioRegistryArtifactType` and `CamelApicurioRegistryLabels` headers. The body is an +`ArtifactSearchResults` holding one page of results and the total `count`. + +Results are paged: the registry returns at most 20 artifacts unless you set +`CamelApicurioRegistrySearchLimit`. Use `CamelApicurioRegistrySearchOffset` to request further +pages, and `CamelApicurioRegistrySearchOrder` (`asc` or `desc`) and +`CamelApicurioRegistrySearchOrderBy` (`groupId`, `artifactId`, `createdOn`, `modifiedOn`, +`artifactType` or `name`) to sort them. + +=== Headers from a consumer + +The consumer sets the group, artifact and version headers on every exchange, and producers give +these headers precedence over their endpoint path. When a route forwards consumed versions to +another registry endpoint, remove them first so the producer uses its own path: + +[source,java] +---- +from("apicurio-registry:dev/orders?registryUrl={{dev.registry}}&fetchContent=true") + .removeHeaders("CamelApicurioRegistry*") + .to("apicurio-registry:prod/orders?registryUrl={{prod.registry}}&operation=updateArtifact"); +---- + == Consumer The consumer polls the registry for new versions of the specified artifact. It tracks the last @@ -97,15 +126,28 @@ On first start, all existing versions of the artifact are emitted as exchanges. only deliver versions created after the consumer started. The watermark is held in memory and resets on route/context restart. -The consumer requests versions newest first, one page at a time, and stops at the first -version it has already seen, so artifacts with many versions are handled without loading -every page on each poll. +Once it has a watermark, each poll first asks only for the newest version, so an idle poll +transfers a single entry. When there is something new, the consumer requests versions newest +first, one page at a time, and stops at the first version it has already seen. Paging is +offset-based: if a version is deleted while the consumer is reading pages, an older new version +can move to a page that was already read and be skipped. + +All version states are listed, so `DRAFT` versions are delivered as soon as they appear. If the +registry allows changing the content of a version, a version is not delivered again after its +content changes. With `fetchContent=true`, `DISABLED` versions are skipped, as are versions whose +content is no longer available (HTTP 404), because the registry does not return their content. If fetching content fails, or the route fails to process a version (including failures handled by the error handler without being rethrown, such as with the default error handler), -the consumer reports the error through its exception handler and stops the current poll -without advancing the watermark. The failed version and any newer ones are delivered again -on the next poll. +the consumer stops the current poll without advancing the watermark. The failed version and any +newer ones are delivered again on the next poll. A version that always fails therefore blocks +newer versions. To skip such a version, mark the failure as handled, which lets the consumer +advance past it: + +[source,java] +---- +onException(MyPermanentException.class).handled(true).to("log:skipped"); +---- When `fetchContent=true`, the consumer fetches the version content and sets it as a `byte[]` exchange body, closing the SDK response stream before processing the exchange. @@ -212,7 +254,7 @@ from("direct:validateSchema") .end(); ---- -== Authentication +== Connection settings All endpoints of the component share a single Vert.x instance for their SDK clients. The component creates it on first use and closes it when the component stops. To reuse an existing @@ -222,6 +264,12 @@ the component does not close a Vert.x instance it did not create. A client supplied through `registryClient` remains caller-owned; the caller is responsible for its underlying HTTP resources. +For HTTPS registries that use a private CA or require client certificates (mTLS), configure +`sslContextParameters`. Use `proxyHost`, `proxyPort`, `proxyUsername` and `proxyPassword` to go +through an HTTP proxy. Both settings also apply to the OAuth2 token endpoint. + +== Authentication + The component supports three authentication modes via the `authType` option: * `none` (default): No authentication. diff --git a/components/camel-apicurio-registry/pom.xml b/components/camel-apicurio-registry/pom.xml index a7e9f7e566211..50531a424ec23 100644 --- a/components/camel-apicurio-registry/pom.xml +++ b/components/camel-apicurio-registry/pom.xml @@ -40,6 +40,11 @@ camel-support + + org.apache.camel + camel-vertx-common + + io.apicurio diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java index d580a0b0227f6..a46160f41d4e4 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponentConfigurer.java @@ -53,9 +53,19 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; case "operation": getOrCreateConfiguration(target).setOperation(property(camelContext, java.lang.String.class, value)); return true; case "password": getOrCreateConfiguration(target).setPassword(property(camelContext, java.lang.String.class, value)); return true; + case "proxyhost": + case "proxyHost": getOrCreateConfiguration(target).setProxyHost(property(camelContext, java.lang.String.class, value)); return true; + case "proxypassword": + case "proxyPassword": getOrCreateConfiguration(target).setProxyPassword(property(camelContext, java.lang.String.class, value)); return true; + case "proxyport": + case "proxyPort": getOrCreateConfiguration(target).setProxyPort(property(camelContext, java.lang.Integer.class, value)); return true; + case "proxyusername": + case "proxyUsername": getOrCreateConfiguration(target).setProxyUsername(property(camelContext, java.lang.String.class, value)); return true; case "registryurl": case "registryUrl": getOrCreateConfiguration(target).setRegistryUrl(property(camelContext, java.lang.String.class, value)); return true; case "scope": getOrCreateConfiguration(target).setScope(property(camelContext, java.lang.String.class, value)); return true; + case "sslcontextparameters": + case "sslContextParameters": getOrCreateConfiguration(target).setSslContextParameters(property(camelContext, org.apache.camel.support.jsse.SSLContextParameters.class, value)); return true; case "tokenendpoint": case "tokenEndpoint": getOrCreateConfiguration(target).setTokenEndpoint(property(camelContext, java.lang.String.class, value)); return true; case "username": getOrCreateConfiguration(target).setUsername(property(camelContext, java.lang.String.class, value)); return true; @@ -95,9 +105,19 @@ public Class getOptionType(String name, boolean ignoreCase) { case "lazyStartProducer": return boolean.class; case "operation": return java.lang.String.class; case "password": return java.lang.String.class; + case "proxyhost": + case "proxyHost": return java.lang.String.class; + case "proxypassword": + case "proxyPassword": return java.lang.String.class; + case "proxyport": + case "proxyPort": return java.lang.Integer.class; + case "proxyusername": + case "proxyUsername": return java.lang.String.class; case "registryurl": case "registryUrl": return java.lang.String.class; case "scope": return java.lang.String.class; + case "sslcontextparameters": + case "sslContextParameters": return org.apache.camel.support.jsse.SSLContextParameters.class; case "tokenendpoint": case "tokenEndpoint": return java.lang.String.class; case "username": return java.lang.String.class; @@ -133,9 +153,19 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "lazyStartProducer": return target.isLazyStartProducer(); case "operation": return getOrCreateConfiguration(target).getOperation(); case "password": return getOrCreateConfiguration(target).getPassword(); + case "proxyhost": + case "proxyHost": return getOrCreateConfiguration(target).getProxyHost(); + case "proxypassword": + case "proxyPassword": return getOrCreateConfiguration(target).getProxyPassword(); + case "proxyport": + case "proxyPort": return getOrCreateConfiguration(target).getProxyPort(); + case "proxyusername": + case "proxyUsername": return getOrCreateConfiguration(target).getProxyUsername(); case "registryurl": case "registryUrl": return getOrCreateConfiguration(target).getRegistryUrl(); case "scope": return getOrCreateConfiguration(target).getScope(); + case "sslcontextparameters": + case "sslContextParameters": return getOrCreateConfiguration(target).getSslContextParameters(); case "tokenendpoint": case "tokenEndpoint": return getOrCreateConfiguration(target).getTokenEndpoint(); case "username": return getOrCreateConfiguration(target).getUsername(); diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java index a82585a675887..43633a06447fa 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointConfigurer.java @@ -59,6 +59,14 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "password": target.getConfiguration().setPassword(property(camelContext, java.lang.String.class, value)); return true; case "pollstrategy": case "pollStrategy": target.setPollStrategy(property(camelContext, org.apache.camel.spi.PollingConsumerPollStrategy.class, value)); return true; + case "proxyhost": + case "proxyHost": target.getConfiguration().setProxyHost(property(camelContext, java.lang.String.class, value)); return true; + case "proxypassword": + case "proxyPassword": target.getConfiguration().setProxyPassword(property(camelContext, java.lang.String.class, value)); return true; + case "proxyport": + case "proxyPort": target.getConfiguration().setProxyPort(property(camelContext, java.lang.Integer.class, value)); return true; + case "proxyusername": + case "proxyUsername": target.getConfiguration().setProxyUsername(property(camelContext, java.lang.String.class, value)); return true; case "registryclient": case "registryClient": target.setRegistryClient(property(camelContext, io.apicurio.registry.rest.client.RegistryClient.class, value)); return true; case "registryurl": @@ -75,6 +83,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "scope": target.getConfiguration().setScope(property(camelContext, java.lang.String.class, value)); return true; case "sendemptymessagewhenidle": case "sendEmptyMessageWhenIdle": target.setSendEmptyMessageWhenIdle(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 "startscheduler": case "startScheduler": target.setStartScheduler(property(camelContext, boolean.class, value)); return true; case "timeunit": @@ -127,6 +137,14 @@ public Class getOptionType(String name, boolean ignoreCase) { case "password": return java.lang.String.class; case "pollstrategy": case "pollStrategy": return org.apache.camel.spi.PollingConsumerPollStrategy.class; + case "proxyhost": + case "proxyHost": return java.lang.String.class; + case "proxypassword": + case "proxyPassword": return java.lang.String.class; + case "proxyport": + case "proxyPort": return java.lang.Integer.class; + case "proxyusername": + case "proxyUsername": return java.lang.String.class; case "registryclient": case "registryClient": return io.apicurio.registry.rest.client.RegistryClient.class; case "registryurl": @@ -143,6 +161,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "scope": return java.lang.String.class; case "sendemptymessagewhenidle": case "sendEmptyMessageWhenIdle": return boolean.class; + case "sslcontextparameters": + case "sslContextParameters": return org.apache.camel.support.jsse.SSLContextParameters.class; case "startscheduler": case "startScheduler": return boolean.class; case "timeunit": @@ -196,6 +216,14 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "password": return target.getConfiguration().getPassword(); case "pollstrategy": case "pollStrategy": return target.getPollStrategy(); + case "proxyhost": + case "proxyHost": return target.getConfiguration().getProxyHost(); + case "proxypassword": + case "proxyPassword": return target.getConfiguration().getProxyPassword(); + case "proxyport": + case "proxyPort": return target.getConfiguration().getProxyPort(); + case "proxyusername": + case "proxyUsername": return target.getConfiguration().getProxyUsername(); case "registryclient": case "registryClient": return target.getRegistryClient(); case "registryurl": @@ -212,6 +240,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "scope": return target.getConfiguration().getScope(); case "sendemptymessagewhenidle": case "sendEmptyMessageWhenIdle": return target.isSendEmptyMessageWhenIdle(); + case "sslcontextparameters": + case "sslContextParameters": return target.getConfiguration().getSslContextParameters(); case "startscheduler": case "startScheduler": return target.isStartScheduler(); case "timeunit": diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java index 41176af63ac2a..f5f406516a38d 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java @@ -24,7 +24,7 @@ public class ApicurioRegistryEndpointUriFactory extends org.apache.camel.support private static final Set ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(36); + Set props = new HashSet<>(41); props.add("artifactId"); props.add("artifactType"); props.add("authType"); @@ -47,6 +47,10 @@ public class ApicurioRegistryEndpointUriFactory extends org.apache.camel.support props.add("operation"); props.add("password"); props.add("pollStrategy"); + props.add("proxyHost"); + props.add("proxyPassword"); + props.add("proxyPort"); + props.add("proxyUsername"); props.add("registryClient"); props.add("registryUrl"); props.add("repeatCount"); @@ -56,15 +60,17 @@ public class ApicurioRegistryEndpointUriFactory extends org.apache.camel.support props.add("schedulerProperties"); props.add("scope"); props.add("sendEmptyMessageWhenIdle"); + props.add("sslContextParameters"); props.add("startScheduler"); props.add("timeUnit"); props.add("tokenEndpoint"); props.add("useFixedDelay"); props.add("username"); PROPERTY_NAMES = Collections.unmodifiableSet(props); - Set secretProps = new HashSet<>(2); + Set secretProps = new HashSet<>(3); secretProps.add("clientSecret"); secretProps.add("password"); + secretProps.add("proxyPassword"); SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps); ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet(); Map prefixes = new HashMap<>(1); diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json index 80e611d92d3dc..9b2684a33b96f 100644 --- a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json @@ -35,13 +35,18 @@ "autowiredEnabled": { "index": 8, "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." }, "configuration": { "index": 9, "kind": "property", "displayName": "Configuration", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "deprecated": false, "autowired": false, "secret": false, "description": "The component configuration" }, "vertx": { "index": 10, "kind": "property", "displayName": "Vertx", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.vertx.core.Vertx", "deprecated": false, "autowired": true, "secret": false, "description": "To use an existing Vert.x instance for the registry clients. If not set, the component creates one shared Vert.x instance on first use and closes it when the component stops." }, - "authType": { "index": 11, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 12, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, - "clientSecret": { "index": 13, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, - "password": { "index": 14, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, - "scope": { "index": 15, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, - "tokenEndpoint": { "index": 16, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 17, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "proxyHost": { "index": 11, "kind": "property", "displayName": "Proxy Host", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The proxy host to use for registry (and OAuth2 token) requests" }, + "proxyPassword": { "index": 12, "kind": "property", "displayName": "Proxy Password", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The password for proxy authentication" }, + "proxyPort": { "index": 13, "kind": "property", "displayName": "Proxy Port", "group": "proxy", "label": "proxy", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The proxy port" }, + "proxyUsername": { "index": 14, "kind": "property", "displayName": "Proxy Username", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The username for proxy authentication" }, + "authType": { "index": 15, "kind": "property", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 16, "kind": "property", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 17, "kind": "property", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 18, "kind": "property", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 19, "kind": "property", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "sslContextParameters": { "index": 20, "kind": "property", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "To configure TLS, such as a trust store for a registry with a private CA or a client certificate (mTLS), using the Camel JSSE configuration. Also applies to the OAuth2 token endpoint." }, + "tokenEndpoint": { "index": 21, "kind": "property", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 22, "kind": "property", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } }, "headers": { "CamelApicurioRegistryOperation": { "index": 0, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The operation to perform", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_OPERATION" }, @@ -57,7 +62,12 @@ "CamelApicurioRegistryContentId": { "index": 10, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content ID", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_CONTENT_ID" }, "CamelApicurioRegistryVersionState": { "index": 11, "kind": "header", "displayName": "", "group": "consumer", "label": "consumer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The version state", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VERSION_STATE" }, "CamelApicurioRegistryValidationResult": { "index": 12, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Whether the check passed. Set by validate and testCompatibility.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_RESULT" }, - "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Rule violation details from validate or testCompatibility. Cleared before each check.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" } + "CamelApicurioRegistryValidationErrors": { "index": 13, "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Rule violation details from validate or testCompatibility. Cleared before each check.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_VALIDATION_ERRORS" }, + "CamelApicurioRegistryLabels": { "index": 14, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Labels to filter searchArtifacts by, as key:value pairs. Either a comma-separated String or a collection.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_LABELS" }, + "CamelApicurioRegistrySearchOffset": { "index": 15, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of results to skip in searchArtifacts. Defaults to 0.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_SEARCH_OFFSET" }, + "CamelApicurioRegistrySearchLimit": { "index": 16, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The maximum number of results returned by searchArtifacts. The registry defaults to 20.", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_SEARCH_LIMIT" }, + "CamelApicurioRegistrySearchOrder": { "index": 17, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The sort order for searchArtifacts: asc or desc", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_SEARCH_ORDER" }, + "CamelApicurioRegistrySearchOrderBy": { "index": 18, "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The field to sort searchArtifacts by: groupId, artifactId, createdOn, modifiedOn, artifactType or name", "constantName": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants#HEADER_SEARCH_ORDER_BY" } }, "properties": { "groupId": { "index": 0, "kind": "path", "displayName": "Group Id", "group": "common", "label": "", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The artifact group ID" }, @@ -75,26 +85,31 @@ "operation": { "index": 12, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "createArtifact", "updateArtifact", "deleteArtifact", "getArtifactContent", "getArtifactMetadata", "searchArtifacts", "listVersions", "createGroup", "testCompatibility", "validate" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The operation to perform" }, "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." }, "registryClient": { "index": 14, "kind": "parameter", "displayName": "Registry Client", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "io.apicurio.registry.rest.client.RegistryClient", "deprecated": false, "autowired": false, "secret": false, "description": "To use a pre-configured RegistryClient instance" }, - "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." }, - "authType": { "index": 29, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, - "clientId": { "index": 30, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, - "clientSecret": { "index": 31, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, - "password": { "index": 32, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, - "scope": { "index": 33, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, - "tokenEndpoint": { "index": 34, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, - "username": { "index": 35, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } + "proxyHost": { "index": 15, "kind": "parameter", "displayName": "Proxy Host", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The proxy host to use for registry (and OAuth2 token) requests" }, + "proxyPassword": { "index": 16, "kind": "parameter", "displayName": "Proxy Password", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The password for proxy authentication" }, + "proxyPort": { "index": 17, "kind": "parameter", "displayName": "Proxy Port", "group": "proxy", "label": "proxy", "required": false, "type": "integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The proxy port" }, + "proxyUsername": { "index": 18, "kind": "parameter", "displayName": "Proxy Username", "group": "proxy", "label": "proxy", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The username for proxy authentication" }, + "backoffErrorThreshold": { "index": 19, "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": 20, "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": 21, "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": 22, "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": 23, "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": 24, "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": 25, "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": 26, "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": 27, "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": 28, "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": 29, "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": 30, "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": 31, "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": 32, "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." }, + "authType": { "index": 33, "kind": "parameter", "displayName": "Auth Type", "group": "security", "label": "security", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "none", "basic", "oidc" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "none", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "The authentication type to use" }, + "clientId": { "index": 34, "kind": "parameter", "displayName": "Client Id", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client ID" }, + "clientSecret": { "index": 35, "kind": "parameter", "displayName": "Client Secret", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 client secret" }, + "password": { "index": 36, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "security": "secret", "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Password for basic authentication" }, + "scope": { "index": 37, "kind": "parameter", "displayName": "Scope", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 scope" }, + "sslContextParameters": { "index": 38, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "To configure TLS, such as a trust store for a registry with a private CA or a client certificate (mTLS), using the Camel JSSE configuration. Also applies to the OAuth2 token endpoint." }, + "tokenEndpoint": { "index": 39, "kind": "parameter", "displayName": "Token Endpoint", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "OAuth2 token endpoint URL" }, + "username": { "index": 40, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration", "configurationField": "configuration", "description": "Username for basic authentication" } } } diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc index 415eacd91109c..0eb34f17436d6 100644 --- a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -61,10 +61,10 @@ the `CamelApicurioRegistryOperation` header: | `createArtifact` | Create a new artifact. Body contains the artifact content. | `updateArtifact` | Create a new version of an existing artifact. Body contains the new content. -| `deleteArtifact` | Delete an artifact. +| `deleteArtifact` | Delete an artifact. Apicurio Registry 3.x disables artifact deletion by default; the registry must run with `apicurio.rest.deletion.artifact.enabled=true` (environment variable `APICURIO_REST_DELETION_ARTIFACT_ENABLED=true`), otherwise the operation fails with HTTP 405. | `getArtifactContent` | Retrieve the content of an artifact version. | `getArtifactMetadata` | Retrieve artifact metadata. -| `searchArtifacts` | Search for artifacts using filter headers. +| `searchArtifacts` | Search for artifacts. See the "Searching artifacts" section below. | `listVersions` | List all versions of an artifact, ordered by global ID. The component follows the registry's pagination and returns every page. | `createGroup` | Create a new group. | `testCompatibility` | Test schema compatibility (dry run). Returns `true`/`false` in the body. @@ -87,6 +87,35 @@ when a check fails and clear any previous value before checking. Both set `CamelApicurioRegistryValidationResult`; `testCompatibility` also returns the boolean result in the body, while `validate` preserves the body. +Operations that act on a specific group or artifact fail with an `IllegalArgumentException` +when the group or artifact ID is set neither in the endpoint path nor in a header. + +=== Searching artifacts + +`searchArtifacts` filters by group ID and artifact ID (endpoint path or headers), and by the +`CamelApicurioRegistryArtifactName`, `CamelApicurioRegistryArtifactDescription`, +`CamelApicurioRegistryArtifactType` and `CamelApicurioRegistryLabels` headers. The body is an +`ArtifactSearchResults` holding one page of results and the total `count`. + +Results are paged: the registry returns at most 20 artifacts unless you set +`CamelApicurioRegistrySearchLimit`. Use `CamelApicurioRegistrySearchOffset` to request further +pages, and `CamelApicurioRegistrySearchOrder` (`asc` or `desc`) and +`CamelApicurioRegistrySearchOrderBy` (`groupId`, `artifactId`, `createdOn`, `modifiedOn`, +`artifactType` or `name`) to sort them. + +=== Headers from a consumer + +The consumer sets the group, artifact and version headers on every exchange, and producers give +these headers precedence over their endpoint path. When a route forwards consumed versions to +another registry endpoint, remove them first so the producer uses its own path: + +[source,java] +---- +from("apicurio-registry:dev/orders?registryUrl={{dev.registry}}&fetchContent=true") + .removeHeaders("CamelApicurioRegistry*") + .to("apicurio-registry:prod/orders?registryUrl={{prod.registry}}&operation=updateArtifact"); +---- + == Consumer The consumer polls the registry for new versions of the specified artifact. It tracks the last @@ -97,15 +126,28 @@ On first start, all existing versions of the artifact are emitted as exchanges. only deliver versions created after the consumer started. The watermark is held in memory and resets on route/context restart. -The consumer requests versions newest first, one page at a time, and stops at the first -version it has already seen, so artifacts with many versions are handled without loading -every page on each poll. +Once it has a watermark, each poll first asks only for the newest version, so an idle poll +transfers a single entry. When there is something new, the consumer requests versions newest +first, one page at a time, and stops at the first version it has already seen. Paging is +offset-based: if a version is deleted while the consumer is reading pages, an older new version +can move to a page that was already read and be skipped. + +All version states are listed, so `DRAFT` versions are delivered as soon as they appear. If the +registry allows changing the content of a version, a version is not delivered again after its +content changes. With `fetchContent=true`, `DISABLED` versions are skipped, as are versions whose +content is no longer available (HTTP 404), because the registry does not return their content. If fetching content fails, or the route fails to process a version (including failures handled by the error handler without being rethrown, such as with the default error handler), -the consumer reports the error through its exception handler and stops the current poll -without advancing the watermark. The failed version and any newer ones are delivered again -on the next poll. +the consumer stops the current poll without advancing the watermark. The failed version and any +newer ones are delivered again on the next poll. A version that always fails therefore blocks +newer versions. To skip such a version, mark the failure as handled, which lets the consumer +advance past it: + +[source,java] +---- +onException(MyPermanentException.class).handled(true).to("log:skipped"); +---- When `fetchContent=true`, the consumer fetches the version content and sets it as a `byte[]` exchange body, closing the SDK response stream before processing the exchange. @@ -212,7 +254,7 @@ from("direct:validateSchema") .end(); ---- -== Authentication +== Connection settings All endpoints of the component share a single Vert.x instance for their SDK clients. The component creates it on first use and closes it when the component stops. To reuse an existing @@ -222,6 +264,12 @@ the component does not close a Vert.x instance it did not create. A client supplied through `registryClient` remains caller-owned; the caller is responsible for its underlying HTTP resources. +For HTTPS registries that use a private CA or require client certificates (mTLS), configure +`sslContextParameters`. Use `proxyHost`, `proxyPort`, `proxyUsername` and `proxyPassword` to go +through an HTTP proxy. Both settings also apply to the OAuth2 token endpoint. + +== Authentication + The component supports three authentication modes via the `authType` option: * `none` (default): No authentication. diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java index e18f7f294faf1..6ed705f779a67 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryComponent.java @@ -38,6 +38,7 @@ public class ApicurioRegistryComponent extends DefaultComponent { private Vertx vertx; private boolean managedVertx; + private Vertx replacedManagedVertx; public ApicurioRegistryComponent() { } @@ -77,24 +78,40 @@ synchronized Vertx getOrCreateVertx() { protected void doStop() throws Exception { super.doStop(); Vertx toClose = null; + Vertx replaced; synchronized (this) { if (managedVertx && vertx != null) { toClose = vertx; vertx = null; managedVertx = false; } + replaced = replacedManagedVertx; + replacedManagedVertx = null; } - if (toClose != null) { - toClose.close().toCompletionStage().toCompletableFuture().get(30, TimeUnit.SECONDS); + closeVertx(toClose); + closeVertx(replaced); + } + + private static void closeVertx(Vertx vertx) throws Exception { + if (vertx != null) { + vertx.close().toCompletionStage().toCompletableFuture().get(30, TimeUnit.SECONDS); } } - public Vertx getVertx() { + public synchronized Vertx getVertx() { return vertx; } - public void setVertx(Vertx vertx) { + /** + * Sets the Vert.x instance to use. The component never closes a Vert.x instance set here. If the component had + * already created its own instance, that one is still closed when the component stops. + */ + public synchronized void setVertx(Vertx vertx) { + if (managedVertx && this.vertx != null && this.vertx != vertx) { + replacedManagedVertx = this.vertx; + } this.vertx = vertx; + this.managedVertx = false; } public ApicurioRegistryConfiguration getConfiguration() { diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java index 4622368747376..5787747606f60 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConfiguration.java @@ -20,6 +20,7 @@ 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; @UriParams public class ApicurioRegistryConfiguration implements Cloneable { @@ -43,7 +44,7 @@ public class ApicurioRegistryConfiguration implements Cloneable { @UriParam(label = "security", description = "Username for basic authentication") private String username; - @UriParam(label = "security", secret = true, description = "Password for basic authentication") + @UriParam(label = "security", security = "secret", description = "Password for basic authentication") private String password; @UriParam(label = "security", description = "OAuth2 token endpoint URL") @@ -52,7 +53,7 @@ public class ApicurioRegistryConfiguration implements Cloneable { @UriParam(label = "security", description = "OAuth2 client ID") private String clientId; - @UriParam(label = "security", secret = true, description = "OAuth2 client secret") + @UriParam(label = "security", security = "secret", description = "OAuth2 client secret") private String clientSecret; @UriParam(label = "security", description = "OAuth2 scope") @@ -74,6 +75,64 @@ public class ApicurioRegistryConfiguration implements Cloneable { defaultValue = "true") private boolean failOnValidation = true; + @UriParam(label = "security", + description = "To configure TLS, such as a trust store for a registry with a private CA or a client" + + " certificate (mTLS), using the Camel JSSE configuration. Also applies to the OAuth2" + + " token endpoint.") + private SSLContextParameters sslContextParameters; + + @UriParam(label = "proxy", description = "The proxy host to use for registry (and OAuth2 token) requests") + private String proxyHost; + + @UriParam(label = "proxy", description = "The proxy port") + private Integer proxyPort; + + @UriParam(label = "proxy", description = "The username for proxy authentication") + private String proxyUsername; + + @UriParam(label = "proxy", security = "secret", description = "The password for proxy authentication") + private String proxyPassword; + + public SSLContextParameters getSslContextParameters() { + return sslContextParameters; + } + + public void setSslContextParameters(SSLContextParameters sslContextParameters) { + this.sslContextParameters = sslContextParameters; + } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public Integer getProxyPort() { + return proxyPort; + } + + public void setProxyPort(Integer proxyPort) { + this.proxyPort = proxyPort; + } + + public String getProxyUsername() { + return proxyUsername; + } + + public void setProxyUsername(String proxyUsername) { + this.proxyUsername = proxyUsername; + } + + public String getProxyPassword() { + return proxyPassword; + } + + public void setProxyPassword(String proxyPassword) { + this.proxyPassword = proxyPassword; + } + public String getRegistryUrl() { return registryUrl; } diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java index 68d1120100834..79a0fff5601d1 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConstants.java @@ -64,6 +64,30 @@ public interface ApicurioRegistryConstants { javaType = "String") String HEADER_VALIDATION_ERRORS = "CamelApicurioRegistryValidationErrors"; + @Metadata(label = "producer", + description = "Labels to filter searchArtifacts by, as key:value pairs. Either a comma-separated String" + + " or a collection.", + javaType = "String") + String HEADER_LABELS = "CamelApicurioRegistryLabels"; + + @Metadata(label = "producer", description = "The number of results to skip in searchArtifacts. Defaults to 0.", + javaType = "Integer") + String HEADER_SEARCH_OFFSET = "CamelApicurioRegistrySearchOffset"; + + @Metadata(label = "producer", + description = "The maximum number of results returned by searchArtifacts. The registry defaults to 20.", + javaType = "Integer") + String HEADER_SEARCH_LIMIT = "CamelApicurioRegistrySearchLimit"; + + @Metadata(label = "producer", description = "The sort order for searchArtifacts: asc or desc", javaType = "String") + String HEADER_SEARCH_ORDER = "CamelApicurioRegistrySearchOrder"; + + @Metadata(label = "producer", + description = "The field to sort searchArtifacts by: groupId, artifactId, createdOn, modifiedOn," + + " artifactType or name", + javaType = "String") + String HEADER_SEARCH_ORDER_BY = "CamelApicurioRegistrySearchOrderBy"; + String OPERATION_CREATE_ARTIFACT = "createArtifact"; String OPERATION_UPDATE_ARTIFACT = "updateArtifact"; String OPERATION_DELETE_ARTIFACT = "deleteArtifact"; diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java index 50d028bc065a1..0739923303c79 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java @@ -23,19 +23,25 @@ import java.util.TreeMap; import io.apicurio.registry.rest.client.RegistryClient; +import io.apicurio.registry.rest.client.models.ProblemDetails; import io.apicurio.registry.rest.client.models.SearchedVersion; import io.apicurio.registry.rest.client.models.SortOrder; import io.apicurio.registry.rest.client.models.VersionSearchResults; import io.apicurio.registry.rest.client.models.VersionSortBy; +import io.apicurio.registry.rest.client.models.VersionState; import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.camel.Processor; import org.apache.camel.support.ScheduledPollConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ApicurioRegistryConsumer extends ScheduledPollConsumer { static final int PAGE_SIZE = 100; + private static final Logger LOG = LoggerFactory.getLogger(ApicurioRegistryConsumer.class); + private final ApicurioRegistryEndpoint endpoint; private final ApicurioRegistryConfiguration configuration; private volatile Long lastSeenGlobalId; @@ -51,62 +57,83 @@ public ApicurioRegistryConsumer(ApicurioRegistryEndpoint endpoint, Processor pro protected int poll() throws Exception { String groupId = endpoint.getGroupId(); String artifactId = endpoint.getArtifactId(); - - if (groupId == null || artifactId == null) { - throw new IllegalArgumentException( - "Both groupId and artifactId are required for the consumer"); - } - RegistryClient client = endpoint.getRegistryClient(); - List versions = fetchNewVersions(client, groupId, artifactId); int count = 0; - for (SearchedVersion version : versions) { + for (SearchedVersion version : fetchNewVersions(client, groupId, artifactId)) { Long globalId = version.getGlobalId(); - if (lastSeenGlobalId == null || globalId > lastSeenGlobalId) { - Exchange exchange = createExchange(false); - try { - Message message = exchange.getIn(); + if (configuration.isFetchContent() && version.getState() == VersionState.DISABLED) { + // the registry answers 404 for the content of a disabled version, so it can never be delivered + LOG.debug("Skipping disabled artifact version {} (globalId {})", version.getVersion(), globalId); + lastSeenGlobalId = globalId; + continue; + } + Exchange exchange = createExchange(false); + try { + Message message = exchange.getIn(); + message.setHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + message.setHeader(ApicurioRegistryConstants.HEADER_VERSION, version.getVersion()); + message.setHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, globalId); + message.setHeader(ApicurioRegistryConstants.HEADER_CONTENT_ID, version.getContentId()); + message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, version.getArtifactType()); + if (version.getState() != null) { + message.setHeader(ApicurioRegistryConstants.HEADER_VERSION_STATE, version.getState().getValue()); + } - message.setHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); - message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); - message.setHeader(ApicurioRegistryConstants.HEADER_VERSION, version.getVersion()); - message.setHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, globalId); - message.setHeader(ApicurioRegistryConstants.HEADER_CONTENT_ID, version.getContentId()); - message.setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, version.getArtifactType()); - if (version.getState() != null) { - message.setHeader(ApicurioRegistryConstants.HEADER_VERSION_STATE, - version.getState().getValue()); + if (configuration.isFetchContent()) { + byte[] content = fetchContent(client, groupId, artifactId, version); + if (content == null) { + lastSeenGlobalId = globalId; + continue; } + message.setBody(content); + } else { + message.setBody(version); + } - if (configuration.isFetchContent()) { - try (InputStream content = client.groups().byGroupId(groupId).artifacts() - .byArtifactId(artifactId).versions() - .byVersionExpression(version.getVersion()).content().get()) { - message.setBody(content.readAllBytes()); - } + getProcessor().process(exchange); + if (exchange.getException() != null) { + // do not advance the watermark so the version is retried on the next poll + if (exchange.getExchangeExtension().isErrorHandlerHandledSet()) { + // the error handler has already logged the failure + LOG.debug("Artifact version with globalId {} failed and will be retried on the next poll", globalId); } else { - message.setBody(version); - } - - getProcessor().process(exchange); - if (exchange.getException() != null) { - // do not advance the watermark so the version is retried on the next poll getExceptionHandler().handleException( "Error processing artifact version with globalId " + globalId, exchange, exchange.getException()); - break; } - lastSeenGlobalId = globalId; - count++; - } finally { - releaseExchange(exchange, false); + break; } + lastSeenGlobalId = globalId; + count++; + } finally { + releaseExchange(exchange, false); } } return count; } + /** + * Fetches the content of a version, or returns null if the version no longer has retrievable content (it was + * disabled or deleted after the version list was read). + */ + private static byte[] fetchContent( + RegistryClient client, String groupId, String artifactId, SearchedVersion version) + throws Exception { + try (InputStream content = client.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) + .versions().byVersionExpression(version.getVersion()).content().get()) { + return content.readAllBytes(); + } catch (ProblemDetails e) { + if (e.getStatus() != null && e.getStatus() == 404) { + LOG.debug("Skipping artifact version {} (globalId {}) whose content is no longer available: {}", + version.getVersion(), version.getGlobalId(), e.getDetail()); + return null; + } + throw e; + } + } + /** * Fetches the versions newer than the watermark, in ascending globalId order. Pages are requested newest first so * that polling stops as soon as an already seen version is reached. @@ -114,6 +141,20 @@ protected int poll() throws Exception { private List fetchNewVersions(RegistryClient client, String groupId, String artifactId) { // keyed by globalId: a version created between page requests shifts the pages and may repeat one entry Map answer = new TreeMap<>(); + if (lastSeenGlobalId != null) { + // cheap probe so that an idle poll only transfers the newest version + VersionSearchResults newest = client.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) + .versions().get(config -> { + config.queryParameters.orderby = VersionSortBy.GlobalId; + config.queryParameters.order = SortOrder.Desc; + config.queryParameters.offset = 0; + config.queryParameters.limit = 1; + }); + if (newest == null || newest.getVersions() == null || newest.getVersions().isEmpty() + || newest.getVersions().get(0).getGlobalId() <= lastSeenGlobalId) { + return List.of(); + } + } int offset = 0; while (true) { final int pageOffset = offset; diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java index 7376567f5c79b..9a24a16031031 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java @@ -16,18 +16,33 @@ */ package org.apache.camel.component.apicurioregistry; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + import io.apicurio.registry.client.RegistryClientFactory; import io.apicurio.registry.client.common.RegistryClientOptions; import io.apicurio.registry.rest.client.RegistryClient; +import io.vertx.core.Vertx; +import io.vertx.core.net.ProxyOptions; +import io.vertx.ext.auth.oauth2.OAuth2Auth; +import io.vertx.ext.auth.oauth2.OAuth2FlowType; +import io.vertx.ext.auth.oauth2.OAuth2Options; +import io.vertx.ext.auth.oauth2.Oauth2Credentials; +import io.vertx.ext.web.client.OAuth2WebClient; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; +import io.vertx.ext.web.client.WebClientSession; import org.apache.camel.Category; import org.apache.camel.Consumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.component.vertx.common.VertxHelper; import org.apache.camel.spi.EndpointServiceLocation; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriPath; import org.apache.camel.support.ScheduledPollEndpoint; +import org.apache.camel.util.ObjectHelper; /** * Manage artifacts, versions, and groups in Apicurio Registry v3. @@ -49,7 +64,8 @@ public class ApicurioRegistryEndpoint extends ScheduledPollEndpoint implements E @UriParam(label = "advanced", description = "To use a pre-configured RegistryClient instance") private RegistryClient registryClient; - private boolean managedRegistryClient; + private WebClient ownedWebClient; + private OAuth2Auth oauth2; ApicurioRegistryEndpoint(String uri, ApicurioRegistryComponent component, ApicurioRegistryConfiguration configuration, @@ -67,6 +83,10 @@ public Producer createProducer() throws Exception { @Override public Consumer createConsumer(Processor processor) throws Exception { + if (ObjectHelper.isEmpty(groupId) || ObjectHelper.isEmpty(artifactId)) { + throw new IllegalArgumentException( + "Both groupId and artifactId are required for the consumer, for example apicurio-registry:myGroup/myArtifact"); + } ApicurioRegistryConsumer consumer = new ApicurioRegistryConsumer(this, processor, configuration); configureConsumer(consumer); return consumer; @@ -76,31 +96,80 @@ public Consumer createConsumer(Processor processor) throws Exception { protected void doStart() throws Exception { super.doStart(); if (registryClient == null) { - registryClient = createRegistryClient(); - managedRegistryClient = true; + Vertx vertx = ((ApicurioRegistryComponent) getComponent()).getOrCreateVertx(); + try { + ownedWebClient = createWebClient(vertx); + registryClient = RegistryClientFactory.create( + RegistryClientOptions.create(configuration.getRegistryUrl(), vertx).customWebClient(ownedWebClient)); + } catch (Exception e) { + closeOwnedResources(); + throw e; + } } } @Override protected void doStop() throws Exception { super.doStop(); - if (managedRegistryClient) { + if (ownedWebClient != null) { registryClient = null; - managedRegistryClient = false; + closeOwnedResources(); + } + } + + private void closeOwnedResources() { + // the SDK client has no close method, so the HTTP connection pool lives in the WebClient we created + if (ownedWebClient != null) { + ownedWebClient.close(); + ownedWebClient = null; + } + if (oauth2 != null) { + oauth2.close(); + oauth2 = null; + } + } + + WebClientOptions createWebClientOptions() throws Exception { + WebClientOptions options = new WebClientOptions(); + if (configuration.getSslContextParameters() != null) { + VertxHelper.setupSSLOptions(getCamelContext(), configuration.getSslContextParameters(), options); + } + if (ObjectHelper.isNotEmpty(configuration.getProxyHost())) { + ProxyOptions proxy = new ProxyOptions().setHost(configuration.getProxyHost()); + if (configuration.getProxyPort() != null) { + proxy.setPort(configuration.getProxyPort()); + } + if (ObjectHelper.isNotEmpty(configuration.getProxyUsername())) { + proxy.setUsername(configuration.getProxyUsername()); + proxy.setPassword(configuration.getProxyPassword()); + } + options.setProxyOptions(proxy); } + return options; } - private RegistryClient createRegistryClient() { - RegistryClientOptions options = RegistryClientOptions.create(configuration.getRegistryUrl(), - ((ApicurioRegistryComponent) getComponent()).getOrCreateVertx()); + private WebClient createWebClient(Vertx vertx) throws Exception { + WebClientOptions options = createWebClientOptions(); + WebClient webClient = WebClient.create(vertx, options); String authType = configuration.getAuthType(); if ("basic".equalsIgnoreCase(authType)) { - options.basicAuth(configuration.getUsername(), configuration.getPassword()); + String credentials = configuration.getUsername() + ":" + configuration.getPassword(); + return WebClientSession.create(webClient).addHeader("Authorization", + "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8))); } else if ("oidc".equalsIgnoreCase(authType)) { - options.oauth2(configuration.getTokenEndpoint(), configuration.getClientId(), - configuration.getClientSecret(), configuration.getScope()); + oauth2 = OAuth2Auth.create(vertx, new OAuth2Options() + .setFlow(OAuth2FlowType.CLIENT) + .setHttpClientOptions(options) + .setClientId(configuration.getClientId()) + .setClientSecret(configuration.getClientSecret()) + .setTokenPath(configuration.getTokenEndpoint())); + Oauth2Credentials credentials = new Oauth2Credentials(); + if (configuration.getScope() != null) { + credentials.addScope(configuration.getScope()); + } + return OAuth2WebClient.create(webClient, oauth2).withCredentials(credentials); } - return RegistryClientFactory.create(options); + return webClient; } public RegistryClient getRegistryClient() { diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java index 48784df14708a..d574a30ff9827 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java @@ -18,10 +18,14 @@ import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactMetaData; +import io.apicurio.registry.rest.client.models.ArtifactSearchResults; +import io.apicurio.registry.rest.client.models.ArtifactSortBy; import io.apicurio.registry.rest.client.models.CreateArtifact; import io.apicurio.registry.rest.client.models.CreateArtifactResponse; import io.apicurio.registry.rest.client.models.CreateGroup; @@ -38,6 +42,7 @@ import org.apache.camel.Message; import org.apache.camel.spi.InvokeOnHeader; import org.apache.camel.support.HeaderSelectorProducer; +import org.apache.camel.util.ObjectHelper; public class ApicurioRegistryProducer extends HeaderSelectorProducer { @@ -67,9 +72,26 @@ private String resolveArtifactId(Message message) { return aid != null ? aid : endpoint.getArtifactId(); } + private String requireGroupId(Message message, String operation) { + return require(resolveGroupId(message), "groupId", ApicurioRegistryConstants.HEADER_GROUP_ID, operation); + } + + private String requireArtifactId(Message message, String operation) { + return require(resolveArtifactId(message), "artifactId", ApicurioRegistryConstants.HEADER_ARTIFACT_ID, operation); + } + + private static String require(String value, String name, String header, String operation) { + if (ObjectHelper.isEmpty(value)) { + throw new IllegalArgumentException( + "The " + operation + " operation requires the " + name + ": set it in the endpoint path or the " + + header + " header"); + } + return value; + } + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT) public void createArtifact(Message message) { - String groupId = resolveGroupId(message); + String groupId = requireGroupId(message, "createArtifact"); String artifactId = resolveArtifactId(message); String artifactType = message.getHeader( ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, configuration.getArtifactType(), String.class); @@ -107,8 +129,8 @@ public void createArtifact(Message message) { @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_UPDATE_ARTIFACT) public void updateArtifact(Message message) { - String groupId = resolveGroupId(message); - String artifactId = resolveArtifactId(message); + String groupId = requireGroupId(message, "updateArtifact"); + String artifactId = requireArtifactId(message, "updateArtifact"); String content = message.getBody(String.class); String version = message.getHeader(ApicurioRegistryConstants.HEADER_VERSION, String.class); String contentType = message.getHeader( @@ -128,15 +150,15 @@ public void updateArtifact(Message message) { @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_DELETE_ARTIFACT) public void deleteArtifact(Message message) { - String groupId = resolveGroupId(message); - String artifactId = resolveArtifactId(message); + String groupId = requireGroupId(message, "deleteArtifact"); + String artifactId = requireArtifactId(message, "deleteArtifact"); getClient().groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).delete(); } @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_GET_ARTIFACT_CONTENT) public void getArtifactContent(Message message) throws Exception { - String groupId = resolveGroupId(message); - String artifactId = resolveArtifactId(message); + String groupId = requireGroupId(message, "getArtifactContent"); + String artifactId = requireArtifactId(message, "getArtifactContent"); String version = message.getHeader( ApicurioRegistryConstants.HEADER_VERSION, "branch=latest", String.class); @@ -148,8 +170,8 @@ public void getArtifactContent(Message message) throws Exception { @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_GET_ARTIFACT_METADATA) public void getArtifactMetadata(Message message) { - String groupId = resolveGroupId(message); - String artifactId = resolveArtifactId(message); + String groupId = requireGroupId(message, "getArtifactMetadata"); + String artifactId = requireArtifactId(message, "getArtifactMetadata"); ArtifactMetaData metadata = getClient().groups().byGroupId(groupId).artifacts() .byArtifactId(artifactId).get(); @@ -158,28 +180,64 @@ public void getArtifactMetadata(Message message) { @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_SEARCH_ARTIFACTS) public void searchArtifacts(Message message) { - var results = getClient().search().artifacts().get(config -> { - String name = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_NAME, String.class); - String groupId = resolveGroupId(message); - String description = message.getHeader( - ApicurioRegistryConstants.HEADER_ARTIFACT_DESCRIPTION, String.class); - if (name != null) { - config.queryParameters.name = name; - } - if (groupId != null) { - config.queryParameters.groupId = groupId; + String name = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_NAME, String.class); + String groupId = resolveGroupId(message); + String artifactId = resolveArtifactId(message); + String description = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_DESCRIPTION, String.class); + // only an explicit header: the artifactType endpoint option has a default that would filter every search + String artifactType = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, String.class); + String[] labels = toLabels(message.getHeader(ApicurioRegistryConstants.HEADER_LABELS)); + Integer offset = message.getHeader(ApicurioRegistryConstants.HEADER_SEARCH_OFFSET, Integer.class); + Integer limit = message.getHeader(ApicurioRegistryConstants.HEADER_SEARCH_LIMIT, Integer.class); + String order = message.getHeader(ApicurioRegistryConstants.HEADER_SEARCH_ORDER, String.class); + String orderBy = message.getHeader(ApicurioRegistryConstants.HEADER_SEARCH_ORDER_BY, String.class); + + ArtifactSearchResults results = getClient().search().artifacts().get(config -> { + var query = config.queryParameters; + query.name = name; + query.groupId = groupId; + query.artifactId = artifactId; + query.description = description; + query.artifactType = artifactType; + query.labels = labels; + query.offset = offset; + query.limit = limit; + if (order != null) { + query.order = parseEnum(SortOrder.forValue(order), order, ApicurioRegistryConstants.HEADER_SEARCH_ORDER); } - if (description != null) { - config.queryParameters.description = description; + if (orderBy != null) { + query.orderby = parseEnum(ArtifactSortBy.forValue(orderBy), orderBy, + ApicurioRegistryConstants.HEADER_SEARCH_ORDER_BY); } }); message.setBody(results); } + private static T parseEnum(T value, String text, String header) { + if (value == null) { + throw new IllegalArgumentException("Unsupported value '" + text + "' for header " + header); + } + return value; + } + + private static String[] toLabels(Object value) { + if (value == null) { + return null; + } + if (value instanceof String[] array) { + return array; + } + if (value instanceof Collection collection) { + return collection.stream().map(String::valueOf).toArray(String[]::new); + } + return Arrays.stream(value.toString().split(",")).map(String::trim).filter(l -> !l.isEmpty()) + .toArray(String[]::new); + } + @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_LIST_VERSIONS) public void listVersions(Message message) { - String groupId = resolveGroupId(message); - String artifactId = resolveArtifactId(message); + String groupId = requireGroupId(message, "listVersions"); + String artifactId = requireArtifactId(message, "listVersions"); List all = new ArrayList<>(); Integer total = null; @@ -214,7 +272,7 @@ public void listVersions(Message message) { @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_CREATE_GROUP) public void createGroup(Message message) { - String groupId = resolveGroupId(message); + String groupId = requireGroupId(message, "createGroup"); String description = message.getHeader( ApicurioRegistryConstants.HEADER_ARTIFACT_DESCRIPTION, String.class); @@ -228,14 +286,14 @@ public void createGroup(Message message) { @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_TEST_COMPATIBILITY) public void testCompatibility(Message message) throws Exception { - boolean compatible = doDryRun(message); + boolean compatible = doDryRun(message, ApicurioRegistryConstants.OPERATION_TEST_COMPATIBILITY); message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, compatible); message.setBody(compatible); } @InvokeOnHeader(ApicurioRegistryConstants.OPERATION_VALIDATE) public void validate(Message message) throws Exception { - boolean valid = doDryRun(message); + boolean valid = doDryRun(message, ApicurioRegistryConstants.OPERATION_VALIDATE); message.setHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT, valid); if (!valid && configuration.isFailOnValidation()) { String errors = message.getHeader( @@ -247,10 +305,10 @@ public void validate(Message message) throws Exception { } } - private boolean doDryRun(Message message) throws Exception { + private boolean doDryRun(Message message, String operation) throws Exception { message.removeHeader(ApicurioRegistryConstants.HEADER_VALIDATION_ERRORS); - String groupId = resolveGroupId(message); - String artifactId = resolveArtifactId(message); + String groupId = requireGroupId(message, operation); + String artifactId = requireArtifactId(message, operation); String content = message.getBody(String.class); String contentType = message.getHeader( ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java index 6f8aa98d6bffc..592eca087b144 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -25,6 +25,9 @@ import java.util.stream.LongStream; import io.apicurio.registry.rest.client.RegistryClient; +import io.apicurio.registry.rest.client.models.ProblemDetails; +import io.apicurio.registry.rest.client.models.SearchedVersion; +import io.apicurio.registry.rest.client.models.VersionState; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.builder.RouteBuilder; @@ -41,6 +44,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -136,12 +140,20 @@ void testPollPagesThroughManyVersionsAndStopsAtWatermark() throws Exception { assertThat(globalIds(received)).containsExactlyElementsOf(LongStream.rangeClosed(1, 250).boxed().toList()); assertThat(pages.requestedOffsets).containsExactly(0, 100, 200); + // an idle poll only probes the newest version pages.requestedOffsets.clear(); + pages.requestedLimits.clear(); + assertThat(consumer.poll()).isZero(); + assertThat(pages.requestedLimits).containsExactly(1); + + pages.requestedOffsets.clear(); + pages.requestedLimits.clear(); pages.add(251); assertThat(consumer.poll()).isEqualTo(1); assertThat(globalIds(received)).endsWith(251L); - // the first page reaches the watermark, so older pages are not fetched again - assertThat(pages.requestedOffsets).containsExactly(0); + // the probe finds a new version, then the first page reaches the watermark, so older pages are not fetched + assertThat(pages.requestedOffsets).containsExactly(0, 0); + assertThat(pages.requestedLimits).containsExactly(1, ApicurioRegistryConsumer.PAGE_SIZE); } @Test @@ -161,14 +173,86 @@ void testFetchContentClosesStream() throws Exception { @ParameterizedTest @ValueSource(strings = { "testGroup", "testGroup/", "/someArtifact" }) - void testMissingIdentifier(String path) throws Exception { + void testMissingIdentifierFailsWhenCreatingConsumer(String path) throws Exception { ApicurioRegistryEndpoint endpoint = context.getEndpoint( "apicurio-registry:" + path + "?registryUrl=http://localhost:8080/apis/registry/v3", ApicurioRegistryEndpoint.class); - ApicurioRegistryConsumer consumer = (ApicurioRegistryConsumer) endpoint.createConsumer(exchange -> { + assertThatThrownBy(() -> endpoint.createConsumer(exchange -> { + })).isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Both groupId and artifactId are required for the consumer"); + } + + @Test + void testFetchContentSkipsDisabledAndMissingVersions() throws Exception { + SearchedVersion disabled = FakeVersionPages.version(2); + disabled.setState(VersionState.DISABLED); + pages.add(1).add(disabled).add(3, 4); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("1").content().get()) + .thenReturn(new ByteArrayInputStream(new byte[] { 1 })); + // a version disabled or deleted after the list was read: the registry answers 404 for its content + ProblemDetails notFound = new ProblemDetails(); + notFound.setStatus(404); + notFound.setName("VersionNotFoundException"); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("3").content().get()).thenThrow(notFound); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("4").content().get()) + .thenReturn(new ByteArrayInputStream(new byte[] { 4 })); + List received = new ArrayList<>(); + ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI + "&fetchContent=true", received); + + assertThat(consumer.poll()).isEqualTo(2); + assertThat(globalIds(received)).containsExactly(1L, 4L); + verify(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("2").content(), never()).get(); + assertThat(consumer.poll()).isZero(); + } + + @Test + void testOtherContentErrorsAreRetried() throws Exception { + pages.add(1); + ProblemDetails unavailable = new ProblemDetails(); + unavailable.setStatus(503); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("1").content().get()) + .thenThrow(unavailable).thenReturn(new ByteArrayInputStream(new byte[] { 1 })); + List received = new ArrayList<>(); + ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI + "&fetchContent=true", received); + + assertThatThrownBy(consumer::poll).isSameAs(unavailable); + assertThat(consumer.poll()).isEqualTo(1); + assertThat(globalIds(received)).containsExactly(1L); + } + + @Test + void testHandledFailureAdvancesPastVersion() throws Exception { + pages.add(1, 2); + AtomicInteger attemptsForFirstVersion = new AtomicInteger(); + ApicurioRegistryEndpoint endpoint = context.getEndpoint(ENDPOINT_URI, ApicurioRegistryEndpoint.class); + endpoint.setRegistryClient(mockClient); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + onException(IllegalStateException.class).handled(true).to("mock:skipped"); + from(ENDPOINT_URI) + .process(exchange -> { + if (exchange.getIn().getHeader(ApicurioRegistryConstants.HEADER_GLOBAL_ID, Long.class) == 1L) { + attemptsForFirstVersion.incrementAndGet(); + throw new IllegalStateException("always fails"); + } + }) + .to("mock:result"); + } }); - assertThatThrownBy(consumer::poll).isInstanceOf(IllegalArgumentException.class) - .hasMessage("Both groupId and artifactId are required for the consumer"); + context.start(); + + MockEndpoint result = context.getEndpoint("mock:result", MockEndpoint.class); + result.expectedHeaderValuesReceivedInAnyOrder(ApicurioRegistryConstants.HEADER_GLOBAL_ID, 2L); + MockEndpoint skipped = context.getEndpoint("mock:skipped", MockEndpoint.class); + skipped.expectedHeaderValuesReceivedInAnyOrder(ApicurioRegistryConstants.HEADER_GLOBAL_ID, 1L); + MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS); + assertThat(attemptsForFirstVersion.get()).isEqualTo(1); } @Test diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java index e97427851f30d..6f0a074b543ef 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java @@ -28,7 +28,12 @@ import io.apicurio.registry.rest.client.RegistryClient; import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.support.jsse.KeyStoreParameters; +import org.apache.camel.support.jsse.SSLContextParameters; +import org.apache.camel.support.jsse.TrustManagersParameters; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; @@ -103,16 +108,27 @@ private void testAuthentication(String options, String authorization, boolean oi } @Test - void testEndpointsShareComponentVertxClosedWithComponent() throws Exception { + void testEndpointsShareComponentVertxAndCloseTheirWebClients() throws Exception { Vertx vertx = mock(Vertx.class); when(vertx.close()).thenReturn(Future.succeededFuture()); List clientVertx = new ArrayList<>(); + List clientWebClients = new ArrayList<>(); + List created = new ArrayList<>(); try (MockedStatic vertxFactory = mockStatic(Vertx.class); + MockedStatic webClientFactory = mockStatic(WebClient.class); MockedStatic clientFactory = mockStatic(RegistryClientFactory.class); DefaultCamelContext context = new DefaultCamelContext()) { vertxFactory.when(Vertx::vertx).thenReturn(vertx); + webClientFactory.when(() -> WebClient.create(any(Vertx.class), any(WebClientOptions.class))).thenAnswer(call -> { + assertThat(call.getArgument(0, Vertx.class)).isSameAs(vertx); + WebClient webClient = mock(WebClient.class); + created.add(webClient); + return webClient; + }); clientFactory.when(() -> RegistryClientFactory.create(any())).thenAnswer(call -> { - clientVertx.add(call.getArgument(0, RegistryClientOptions.class).getVertx()); + RegistryClientOptions options = call.getArgument(0, RegistryClientOptions.class); + clientVertx.add(options.getVertx()); + clientWebClients.add(options.getWebClient()); return mock(RegistryClient.class); }); context.start(); @@ -123,16 +139,23 @@ void testEndpointsShareComponentVertxClosedWithComponent() throws Exception { first.start(); second.start(); assertThat(clientVertx).containsExactly(vertx, vertx); + assertThat(clientWebClients).containsExactlyElementsOf(created); vertxFactory.verify(Vertx::vertx, times(1)); - // stopping and restarting an endpoint recreates its client but keeps the shared Vert.x instance + // stopping an endpoint closes its WebClient, but keeps the shared Vert.x instance first.stop(); assertThat(first.getRegistryClient()).isNull(); + verify(created.get(0)).close(); + verify(created.get(1), never()).close(); first.start(); assertThat(first.getRegistryClient()).isNotNull(); + assertThat(created).hasSize(3); verify(vertx, never()).close(); vertxFactory.verify(Vertx::vertx, times(1)); + second.stop(); + first.stop(); + created.forEach(webClient -> verify(webClient).close()); context.getComponent("apicurio-registry").stop(); verify(vertx).close(); } @@ -142,8 +165,11 @@ void testEndpointsShareComponentVertxClosedWithComponent() throws Exception { void testProvidedVertxIsNotClosed() throws Exception { Vertx vertx = mock(Vertx.class); try (MockedStatic vertxFactory = mockStatic(Vertx.class); + MockedStatic webClientFactory = mockStatic(WebClient.class); MockedStatic clientFactory = mockStatic(RegistryClientFactory.class); DefaultCamelContext context = new DefaultCamelContext()) { + webClientFactory.when(() -> WebClient.create(any(Vertx.class), any(WebClientOptions.class))) + .thenAnswer(call -> mock(WebClient.class)); clientFactory.when(() -> RegistryClientFactory.create(any())).thenAnswer(call -> { assertThat(call.getArgument(0, RegistryClientOptions.class).getVertx()).isSameAs(vertx); return mock(RegistryClient.class); @@ -159,6 +185,54 @@ void testProvidedVertxIsNotClosed() throws Exception { } } + @Test + void testVertxSetAfterComponentCreatedOneIsNotClosed() throws Exception { + Vertx managed = mock(Vertx.class); + when(managed.close()).thenReturn(Future.succeededFuture()); + Vertx provided = mock(Vertx.class); + try (MockedStatic vertxFactory = mockStatic(Vertx.class); + DefaultCamelContext context = new DefaultCamelContext()) { + vertxFactory.when(Vertx::vertx).thenReturn(managed); + ApicurioRegistryComponent component = context.getComponent("apicurio-registry", ApicurioRegistryComponent.class); + context.start(); + assertThat(component.getOrCreateVertx()).isSameAs(managed); + + component.setVertx(provided); + assertThat(component.getOrCreateVertx()).isSameAs(provided); + component.stop(); + verify(provided, never()).close(); + // the instance the component created itself is still closed + verify(managed).close(); + } + } + + @Test + void testTlsAndProxyOptions() throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + SSLContextParameters ssl = new SSLContextParameters(); + KeyStoreParameters trustStore = new KeyStoreParameters(); + trustStore.setResource("classpath:truststore.p12"); + trustStore.setType("PKCS12"); + trustStore.setPassword("changeit"); + TrustManagersParameters trustManagers = new TrustManagersParameters(); + trustManagers.setKeyStore(trustStore); + ssl.setTrustManagers(trustManagers); + context.getRegistry().bind("ssl", ssl); + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:g/a?registryUrl=https://registry:8443/apis/registry/v3&sslContextParameters=#ssl" + + "&proxyHost=proxy&proxyPort=3128&proxyUsername=user&proxyPassword=pass", + ApicurioRegistryEndpoint.class); + + WebClientOptions options = endpoint.createWebClientOptions(); + assertThat(options.isSsl()).isTrue(); + assertThat(options.getTrustOptions()).isNotNull(); + assertThat(options.getProxyOptions().getHost()).isEqualTo("proxy"); + assertThat(options.getProxyOptions().getPort()).isEqualTo(3128); + assertThat(options.getProxyOptions().getUsername()).isEqualTo("user"); + assertThat(options.getProxyOptions().getPassword()).isEqualTo("pass"); + } + } + @Test void testCallerOwnedClientSurvivesRestart() throws Exception { RegistryClient client = mock(RegistryClient.class); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java index c9e6f7b34531b..0b93248496d2f 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java @@ -28,6 +28,7 @@ import io.apicurio.registry.rest.client.groups.item.artifacts.ArtifactsRequestBuilder; import io.apicurio.registry.rest.client.models.ArtifactMetaData; import io.apicurio.registry.rest.client.models.ArtifactSearchResults; +import io.apicurio.registry.rest.client.models.ArtifactSortBy; import io.apicurio.registry.rest.client.models.CreateArtifact; import io.apicurio.registry.rest.client.models.CreateArtifactResponse; import io.apicurio.registry.rest.client.models.CreateGroup; @@ -37,6 +38,7 @@ import io.apicurio.registry.rest.client.models.ProblemDetails; import io.apicurio.registry.rest.client.models.RuleViolationProblemDetails; import io.apicurio.registry.rest.client.models.SearchedVersion; +import io.apicurio.registry.rest.client.models.SortOrder; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; import org.apache.camel.BindToRegistry; @@ -272,6 +274,72 @@ void testSearchArtifacts() throws Exception { assertThat(result).isSameAs(mockResults); } + @Test + void testSearchArtifactsAppliesFiltersAndPaging() throws Exception { + injectMockClient( + "apicurio-registry:testGroup?registryUrl=http://localhost:8080/apis/registry/v3&operation=searchArtifacts"); + ArtifactSearchResults mockResults = new ArtifactSearchResults(); + when(mockClient.search().artifacts().get(any())).thenAnswer(call -> { + io.apicurio.registry.rest.client.search.artifacts.ArtifactsRequestBuilder builder = mockClient.search().artifacts(); + var config = builder.new GetRequestConfiguration(); + Consumer configurer + = call.getArgument(0); + configurer.accept(config); + var query = config.queryParameters; + assertThat(query.groupId).isEqualTo("testGroup"); + assertThat(query.artifactId).isEqualTo("orders"); + assertThat(query.name).isEqualTo("Orders"); + assertThat(query.artifactType).isEqualTo("AVRO"); + assertThat(query.labels).containsExactly("team:payments", "env:prod"); + assertThat(query.offset).isEqualTo(40); + assertThat(query.limit).isEqualTo(50); + assertThat(query.order).isEqualTo(SortOrder.Desc); + assertThat(query.orderby).isEqualTo(ArtifactSortBy.CreatedOn); + return mockResults; + }); + + Object result = template.requestBodyAndHeaders("direct:searchArtifacts", null, Map.of( + ApicurioRegistryConstants.HEADER_ARTIFACT_ID, "orders", + ApicurioRegistryConstants.HEADER_ARTIFACT_NAME, "Orders", + ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "AVRO", + ApicurioRegistryConstants.HEADER_LABELS, "team:payments, env:prod", + ApicurioRegistryConstants.HEADER_SEARCH_OFFSET, 40, + ApicurioRegistryConstants.HEADER_SEARCH_LIMIT, "50", + ApicurioRegistryConstants.HEADER_SEARCH_ORDER, "desc", + ApicurioRegistryConstants.HEADER_SEARCH_ORDER_BY, "createdOn")); + assertThat(result).isSameAs(mockResults); + } + + @Test + void testSearchArtifactsRejectsUnknownSort() throws Exception { + injectMockClient( + "apicurio-registry:testGroup?registryUrl=http://localhost:8080/apis/registry/v3&operation=searchArtifacts"); + when(mockClient.search().artifacts().get(any())).thenAnswer(call -> { + io.apicurio.registry.rest.client.search.artifacts.ArtifactsRequestBuilder builder = mockClient.search().artifacts(); + Consumer configurer + = call.getArgument(0); + configurer.accept(builder.new GetRequestConfiguration()); + return new ArtifactSearchResults(); + }); + + assertThatThrownBy(() -> template.requestBodyAndHeader("direct:searchArtifacts", null, + ApicurioRegistryConstants.HEADER_SEARCH_ORDER_BY, "size")) + .hasCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage("Unsupported value 'size' for header CamelApicurioRegistrySearchOrderBy"); + } + + @ParameterizedTest + @ValueSource(strings = { "getArtifactMetadata", "deleteArtifact", "listVersions", "validate" }) + void testMissingArtifactIdFailsClearly(String operation) throws Exception { + injectMockClient( + "apicurio-registry:testGroup?registryUrl=http://localhost:8080/apis/registry/v3&operation=searchArtifacts"); + assertThatThrownBy(() -> template.requestBodyAndHeader("direct:searchArtifacts", "{}", + ApicurioRegistryConstants.HEADER_OPERATION, operation)) + .hasCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage("The " + operation + " operation requires the artifactId: set it in the endpoint path" + + " or the CamelApicurioRegistryArtifactId header"); + } + @Test void testTestCompatibilitySuccess() throws Exception { String endpointUri diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/FakeVersionPages.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/FakeVersionPages.java index ce3e48d509433..bcd7156b52663 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/FakeVersionPages.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/FakeVersionPages.java @@ -40,6 +40,7 @@ final class FakeVersionPages { final List versions = new ArrayList<>(); final List requestedOffsets = new ArrayList<>(); + final List requestedLimits = new ArrayList<>(); @SuppressWarnings("unchecked") FakeVersionPages(VersionsRequestBuilder builder) { @@ -63,6 +64,11 @@ FakeVersionPages add(long... globalIds) { return this; } + FakeVersionPages add(SearchedVersion version) { + versions.add(version); + return this; + } + FakeVersionPages addRange(long fromInclusive, long toInclusive) { for (long id = fromInclusive; id <= toInclusive; id++) { versions.add(version(id)); @@ -84,6 +90,7 @@ private VersionSearchResults page( int offset = config.queryParameters.offset != null ? config.queryParameters.offset : 0; int limit = config.queryParameters.limit != null ? config.queryParameters.limit : REGISTRY_DEFAULT_LIMIT; requestedOffsets.add(offset); + requestedLimits.add(limit); VersionSearchResults results = new VersionSearchResults(); results.setCount(sorted.size()); results.setVersions(sorted.subList(Math.min(offset, sorted.size()), Math.min(offset + limit, sorted.size()))); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java index de00671454d35..1ba743cb7adda 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryProducerIT.java @@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -169,44 +170,60 @@ void testFullArtifactLifecycle() throws Exception { @Test void testSearchArtifacts() throws Exception { String groupId = "search-group-" + UUID.randomUUID(); - String artifactId = "search-artifact-" + UUID.randomUUID(); - - // Create group first - Map groupHeaders = new HashMap<>(); - groupHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_GROUP); - groupHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); - template.request("direct:operation", exchange -> exchange.getIn().setHeaders(groupHeaders)); + String suffix = UUID.randomUUID().toString(); + List artifactIds = List.of("a-" + suffix, "b-" + suffix, "c-" + suffix); + + operation(ApicurioRegistryConstants.OPERATION_CREATE_GROUP, groupId, null, null); + for (String artifactId : artifactIds) { + Exchange created = operation(ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT, groupId, artifactId, + Map.of(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "JSON")); + assertThat(created.getException()).isNull(); + } + + // the artifact ID filter is applied + ArtifactSearchResults byId = search(groupId, artifactIds.get(1), Map.of()); + assertThat(byId.getArtifacts()).extracting("artifactId").containsExactly(artifactIds.get(1)); + + // results are paged and sorted + Map firstPageHeaders = Map.of( + ApicurioRegistryConstants.HEADER_SEARCH_LIMIT, 2, + ApicurioRegistryConstants.HEADER_SEARCH_ORDER_BY, "artifactId", + ApicurioRegistryConstants.HEADER_SEARCH_ORDER, "asc"); + ArtifactSearchResults firstPage = search(groupId, null, firstPageHeaders); + assertThat(firstPage.getCount()).isEqualTo(3); + assertThat(firstPage.getArtifacts()).extracting("artifactId").containsExactlyElementsOf(artifactIds.subList(0, 2)); + + Map secondPageHeaders = new HashMap<>(firstPageHeaders); + secondPageHeaders.put(ApicurioRegistryConstants.HEADER_SEARCH_OFFSET, 2); + ArtifactSearchResults secondPage = search(groupId, null, secondPageHeaders); + assertThat(secondPage.getArtifacts()).extracting("artifactId").containsExactly(artifactIds.get(2)); + + for (String artifactId : artifactIds) { + Exchange deleted = operation(ApicurioRegistryConstants.OPERATION_DELETE_ARTIFACT, groupId, artifactId, null); + assertThat(deleted.getException()).isNull(); + } + } - // Create artifact - Map createHeaders = new HashMap<>(); - createHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT); - createHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); - createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); - createHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, "JSON"); - template.request("direct:operation", exchange -> { - exchange.getIn().setHeaders(createHeaders); - exchange.getIn().setBody(JSON_SCHEMA); - }); + private ArtifactSearchResults search(String groupId, String artifactId, Map extraHeaders) { + Exchange result = operation(ApicurioRegistryConstants.OPERATION_SEARCH_ARTIFACTS, groupId, artifactId, extraHeaders); + assertThat(result.getException()).isNull(); + return result.getIn().getBody(ArtifactSearchResults.class); + } - // Search - Map searchHeaders = new HashMap<>(); - searchHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_SEARCH_ARTIFACTS); - searchHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); - searchHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); - Exchange searchResult = template.request("direct:operation", exchange -> { - exchange.getIn().setHeaders(searchHeaders); + private Exchange operation(String operation, String groupId, String artifactId, Map extraHeaders) { + return template.request("direct:operation", exchange -> { + exchange.getIn().setHeader(ApicurioRegistryConstants.HEADER_OPERATION, operation); + exchange.getIn().setHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + if (artifactId != null) { + exchange.getIn().setHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + } + if (extraHeaders != null) { + extraHeaders.forEach(exchange.getIn()::setHeader); + } + if (ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT.equals(operation)) { + exchange.getIn().setBody(JSON_SCHEMA); + } }); - assertThat(searchResult.getException()).isNull(); - ArtifactSearchResults results = searchResult.getIn().getBody(ArtifactSearchResults.class); - assertThat(results.getArtifacts()).extracting("artifactId").containsExactly(artifactId); - - // Cleanup - Map deleteHeaders = new HashMap<>(); - deleteHeaders.put(ApicurioRegistryConstants.HEADER_OPERATION, ApicurioRegistryConstants.OPERATION_DELETE_ARTIFACT); - deleteHeaders.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); - deleteHeaders.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); - Exchange deleted = template.request("direct:operation", exchange -> exchange.getIn().setHeaders(deleteHeaders)); - assertThat(deleted.getException()).isNull(); } @ParameterizedTest diff --git a/components/camel-apicurio-registry/src/test/resources/truststore.p12 b/components/camel-apicurio-registry/src/test/resources/truststore.p12 new file mode 100644 index 0000000000000000000000000000000000000000..6f477b0df7318f5e70dec79687576eb28ab2b6a4 GIT binary patch literal 1094 zcmXqLVsT<(WHxAGe#6G8)#lOmotKfFaX}OFU6v;1n?T{KKztD)#Ril*1QgoG#K-`o zwj*R14btEoR$hZh0}BL~$3UA!WR+$FZ?C1v$CtBp-ma=Bv)<-)(}IafU4V&E!GMR2 z17bN7Co6-2EE{J+n+IbmGYg{@i@-JAuYy@IjwNpn+lz``T%6L-#O&Z}x%bkk_7Z#X ze~l-nD5*s~FUp*KGeEb#=5OM4bNQZc8;lJXeTk}D)c&+qBB_7{Vn^472Q%rY znK@mM?pyo2eM3ST_x}#Pxd(ehC+@O3p<9$4J9E|ciQlvR+tXKxd@r1mG9kzR!u;je zRm>K9Nq#K;vaWQ~izx~|mifmQoHVE(qo70byEpDqYAZ~ADZQ9k#fO?}kCw!3U{PcCjy zoa@pbV$*xroZo!1%G`4gWpw|AepEaYd!cGouR!Gyx!moWc~nj~pJjgIaMZCl*zCoP z1FMBy3sS{cxZi$1An{i7Pk+`&vs*V#z1ygi!7TX6c+#`!yw`KC|KX7~kDYN<#U|so z>ZHh9zgg;ygp7UE%jU>T*}E@QG-zh3t$RaF%z>DOMv*_Ne_K-5Nx$`xm@JdO(DBH5 zJKrm(a|9ON4Ov&cr?>RVg5o556{U#<#mhpNYF{{PD-_DC-@bZ!nu*$yzbmQ&3S;~< ze5XhLe(CsNp`GyB{g1p37&>R))V#>^C@tooeBrLP?ROi0-JGQp`HQu#HZ1si)nc#T zQjLB}6Bvq>kE~k1Vy^O*{9QNJtIU_9^L6J$v_BUzQ`|jqd(j=uHG4|t z@NSSjRCD!Bd(5LFX97em%^QzDot+){;BchW?m)i7k=87?=l(6#*?mzXcZb3Jly=b< z*Q1i0J@`zVmc01bczj9s?K8r&pG`_^{C45x=ehS!314L3N;jXLyhA~wXvfzB7f*b- zx?%m>Y4dqze%_d7^fvmcxntZJlOZvHku0Y^7FD5vxpp-t}EoU{>bUoo_8XI-G%n* K@g_U~Wnlo)^Y0x1 literal 0 HcmV?d00001 diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java index 1c59cddcf2e5a..505549db1bd5d 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java @@ -258,6 +258,66 @@ default ApicurioRegistryComponentBuilder vertx(io.vertx.core.Vertx vertx) { return this; } + /** + * The proxy host to use for registry (and OAuth2 token) requests. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: proxy + * + * @param proxyHost the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder proxyHost(java.lang.String proxyHost) { + doSetProperty("proxyHost", proxyHost); + return this; + } + + /** + * The password for proxy authentication. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: proxy + * + * @param proxyPassword the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder proxyPassword(java.lang.String proxyPassword) { + doSetProperty("proxyPassword", proxyPassword); + return this; + } + + /** + * The proxy port. + * + * The option is a: <code>java.lang.Integer</code> type. + * + * Group: proxy + * + * @param proxyPort the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder proxyPort(java.lang.Integer proxyPort) { + doSetProperty("proxyPort", proxyPort); + return this; + } + + /** + * The username for proxy authentication. + * + * The option is a: <code>java.lang.String</code> type. + * + * Group: proxy + * + * @param proxyUsername the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder proxyUsername(java.lang.String proxyUsername) { + doSetProperty("proxyUsername", proxyUsername); + return this; + } + /** * The authentication type to use. @@ -335,6 +395,24 @@ default ApicurioRegistryComponentBuilder scope(java.lang.String scope) { return this; } + /** + * To configure TLS, such as a trust store for a registry with a private + * CA or a client certificate (mTLS), using the Camel JSSE + * configuration. Also applies to the OAuth2 token endpoint. + * + * The option is a: + * <code>org.apache.camel.support.jsse.SSLContextParameters</code> type. + * + * Group: security + * + * @param sslContextParameters the value to set + * @return the dsl builder + */ + default ApicurioRegistryComponentBuilder sslContextParameters(org.apache.camel.support.jsse.SSLContextParameters sslContextParameters) { + doSetProperty("sslContextParameters", sslContextParameters); + return this; + } + /** * OAuth2 token endpoint URL. * @@ -396,11 +474,16 @@ protected boolean setPropertyOnComponent( case "autowiredEnabled": ((ApicurioRegistryComponent) component).setAutowiredEnabled((boolean) value); return true; case "configuration": ((ApicurioRegistryComponent) component).setConfiguration((org.apache.camel.component.apicurioregistry.ApicurioRegistryConfiguration) value); return true; case "vertx": ((ApicurioRegistryComponent) component).setVertx((io.vertx.core.Vertx) value); return true; + case "proxyHost": getOrCreateConfiguration((ApicurioRegistryComponent) component).setProxyHost((java.lang.String) value); return true; + case "proxyPassword": getOrCreateConfiguration((ApicurioRegistryComponent) component).setProxyPassword((java.lang.String) value); return true; + case "proxyPort": getOrCreateConfiguration((ApicurioRegistryComponent) component).setProxyPort((java.lang.Integer) value); return true; + case "proxyUsername": getOrCreateConfiguration((ApicurioRegistryComponent) component).setProxyUsername((java.lang.String) value); return true; case "authType": getOrCreateConfiguration((ApicurioRegistryComponent) component).setAuthType((java.lang.String) value); return true; case "clientId": getOrCreateConfiguration((ApicurioRegistryComponent) component).setClientId((java.lang.String) value); return true; case "clientSecret": getOrCreateConfiguration((ApicurioRegistryComponent) component).setClientSecret((java.lang.String) value); return true; case "password": getOrCreateConfiguration((ApicurioRegistryComponent) component).setPassword((java.lang.String) value); return true; case "scope": getOrCreateConfiguration((ApicurioRegistryComponent) component).setScope((java.lang.String) value); return true; + case "sslContextParameters": getOrCreateConfiguration((ApicurioRegistryComponent) component).setSslContextParameters((org.apache.camel.support.jsse.SSLContextParameters) value); return true; case "tokenEndpoint": getOrCreateConfiguration((ApicurioRegistryComponent) component).setTokenEndpoint((java.lang.String) value); return true; case "username": getOrCreateConfiguration((ApicurioRegistryComponent) component).setUsername((java.lang.String) value); return true; default: return false; diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java index 1662c75f196cc..b97a2443b9e79 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java @@ -122,6 +122,77 @@ default ApicurioRegistryEndpointConsumerBuilder sendEmptyMessageWhenIdle(String doSetProperty("sendEmptyMessageWhenIdle", sendEmptyMessageWhenIdle); return this; } + /** + * The proxy host to use for registry (and OAuth2 token) requests. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyHost the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder proxyHost(String proxyHost) { + doSetProperty("proxyHost", proxyHost); + return this; + } + /** + * The password for proxy authentication. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyPassword the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder proxyPassword(String proxyPassword) { + doSetProperty("proxyPassword", proxyPassword); + return this; + } + /** + * The proxy port. + * + * The option is a: java.lang.Integer type. + * + * Group: proxy + * + * @param proxyPort the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder proxyPort(Integer proxyPort) { + doSetProperty("proxyPort", proxyPort); + return this; + } + /** + * The proxy port. + * + * The option will be converted to a java.lang.Integer + * type. + * + * Group: proxy + * + * @param proxyPort the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder proxyPort(String proxyPort) { + doSetProperty("proxyPort", proxyPort); + return this; + } + /** + * The username for proxy authentication. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyUsername the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder proxyUsername(String proxyUsername) { + doSetProperty("proxyUsername", proxyUsername); + return this; + } /** * The number of subsequent error polls (failed due some error) that * should happen before the backoffMultipler should kick-in. @@ -650,6 +721,40 @@ default ApicurioRegistryEndpointConsumerBuilder scope(String scope) { doSetProperty("scope", scope); return this; } + /** + * To configure TLS, such as a trust store for a registry with a private + * CA or a client certificate (mTLS), using the Camel JSSE + * configuration. Also applies to the OAuth2 token endpoint. + * + * The option is a: + * org.apache.camel.support.jsse.SSLContextParameters type. + * + * Group: security + * + * @param sslContextParameters the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder sslContextParameters(org.apache.camel.support.jsse.SSLContextParameters sslContextParameters) { + doSetProperty("sslContextParameters", sslContextParameters); + return this; + } + /** + * To configure TLS, such as a trust store for a registry with a private + * CA or a client certificate (mTLS), using the Camel JSSE + * configuration. Also applies to the OAuth2 token endpoint. + * + * The option will be converted to a + * org.apache.camel.support.jsse.SSLContextParameters type. + * + * Group: security + * + * @param sslContextParameters the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointConsumerBuilder sslContextParameters(String sslContextParameters) { + doSetProperty("sslContextParameters", sslContextParameters); + return this; + } /** * OAuth2 token endpoint URL. * @@ -975,6 +1080,77 @@ default ApicurioRegistryEndpointProducerBuilder operation(String operation) { doSetProperty("operation", operation); return this; } + /** + * The proxy host to use for registry (and OAuth2 token) requests. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyHost the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder proxyHost(String proxyHost) { + doSetProperty("proxyHost", proxyHost); + return this; + } + /** + * The password for proxy authentication. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyPassword the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder proxyPassword(String proxyPassword) { + doSetProperty("proxyPassword", proxyPassword); + return this; + } + /** + * The proxy port. + * + * The option is a: java.lang.Integer type. + * + * Group: proxy + * + * @param proxyPort the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder proxyPort(Integer proxyPort) { + doSetProperty("proxyPort", proxyPort); + return this; + } + /** + * The proxy port. + * + * The option will be converted to a java.lang.Integer + * type. + * + * Group: proxy + * + * @param proxyPort the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder proxyPort(String proxyPort) { + doSetProperty("proxyPort", proxyPort); + return this; + } + /** + * The username for proxy authentication. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyUsername the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder proxyUsername(String proxyUsername) { + doSetProperty("proxyUsername", proxyUsername); + return this; + } /** * The authentication type to use. * @@ -1046,6 +1222,40 @@ default ApicurioRegistryEndpointProducerBuilder scope(String scope) { doSetProperty("scope", scope); return this; } + /** + * To configure TLS, such as a trust store for a registry with a private + * CA or a client certificate (mTLS), using the Camel JSSE + * configuration. Also applies to the OAuth2 token endpoint. + * + * The option is a: + * org.apache.camel.support.jsse.SSLContextParameters type. + * + * Group: security + * + * @param sslContextParameters the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder sslContextParameters(org.apache.camel.support.jsse.SSLContextParameters sslContextParameters) { + doSetProperty("sslContextParameters", sslContextParameters); + return this; + } + /** + * To configure TLS, such as a trust store for a registry with a private + * CA or a client certificate (mTLS), using the Camel JSSE + * configuration. Also applies to the OAuth2 token endpoint. + * + * The option will be converted to a + * org.apache.camel.support.jsse.SSLContextParameters type. + * + * Group: security + * + * @param sslContextParameters the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointProducerBuilder sslContextParameters(String sslContextParameters) { + doSetProperty("sslContextParameters", sslContextParameters); + return this; + } /** * OAuth2 token endpoint URL. * @@ -1188,6 +1398,77 @@ default ApicurioRegistryEndpointBuilder registryUrl(String registryUrl) { doSetProperty("registryUrl", registryUrl); return this; } + /** + * The proxy host to use for registry (and OAuth2 token) requests. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyHost the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder proxyHost(String proxyHost) { + doSetProperty("proxyHost", proxyHost); + return this; + } + /** + * The password for proxy authentication. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyPassword the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder proxyPassword(String proxyPassword) { + doSetProperty("proxyPassword", proxyPassword); + return this; + } + /** + * The proxy port. + * + * The option is a: java.lang.Integer type. + * + * Group: proxy + * + * @param proxyPort the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder proxyPort(Integer proxyPort) { + doSetProperty("proxyPort", proxyPort); + return this; + } + /** + * The proxy port. + * + * The option will be converted to a java.lang.Integer + * type. + * + * Group: proxy + * + * @param proxyPort the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder proxyPort(String proxyPort) { + doSetProperty("proxyPort", proxyPort); + return this; + } + /** + * The username for proxy authentication. + * + * The option is a: java.lang.String type. + * + * Group: proxy + * + * @param proxyUsername the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder proxyUsername(String proxyUsername) { + doSetProperty("proxyUsername", proxyUsername); + return this; + } /** * The authentication type to use. * @@ -1259,6 +1540,40 @@ default ApicurioRegistryEndpointBuilder scope(String scope) { doSetProperty("scope", scope); return this; } + /** + * To configure TLS, such as a trust store for a registry with a private + * CA or a client certificate (mTLS), using the Camel JSSE + * configuration. Also applies to the OAuth2 token endpoint. + * + * The option is a: + * org.apache.camel.support.jsse.SSLContextParameters type. + * + * Group: security + * + * @param sslContextParameters the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder sslContextParameters(org.apache.camel.support.jsse.SSLContextParameters sslContextParameters) { + doSetProperty("sslContextParameters", sslContextParameters); + return this; + } + /** + * To configure TLS, such as a trust store for a registry with a private + * CA or a client certificate (mTLS), using the Camel JSSE + * configuration. Also applies to the OAuth2 token endpoint. + * + * The option will be converted to a + * org.apache.camel.support.jsse.SSLContextParameters type. + * + * Group: security + * + * @param sslContextParameters the value to set + * @return the dsl builder + */ + default ApicurioRegistryEndpointBuilder sslContextParameters(String sslContextParameters) { + doSetProperty("sslContextParameters", sslContextParameters); + return this; + } /** * OAuth2 token endpoint URL. * @@ -1577,6 +1892,69 @@ public String apicurioRegistryValidationResult() { public String apicurioRegistryValidationErrors() { return "CamelApicurioRegistryValidationErrors"; } + /** + * Labels to filter searchArtifacts by, as key:value pairs. Either a + * comma-separated String or a collection. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code ApicurioRegistryLabels}. + */ + public String apicurioRegistryLabels() { + return "CamelApicurioRegistryLabels"; + } + /** + * The number of results to skip in searchArtifacts. Defaults to 0. + * + * The option is a: {@code Integer} type. + * + * Group: producer + * + * @return the name of the header {@code ApicurioRegistrySearchOffset}. + */ + public String apicurioRegistrySearchOffset() { + return "CamelApicurioRegistrySearchOffset"; + } + /** + * The maximum number of results returned by searchArtifacts. The + * registry defaults to 20. + * + * The option is a: {@code Integer} type. + * + * Group: producer + * + * @return the name of the header {@code ApicurioRegistrySearchLimit}. + */ + public String apicurioRegistrySearchLimit() { + return "CamelApicurioRegistrySearchLimit"; + } + /** + * The sort order for searchArtifacts: asc or desc. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code ApicurioRegistrySearchOrder}. + */ + public String apicurioRegistrySearchOrder() { + return "CamelApicurioRegistrySearchOrder"; + } + /** + * The field to sort searchArtifacts by: groupId, artifactId, createdOn, + * modifiedOn, artifactType or name. + * + * The option is a: {@code String} type. + * + * Group: producer + * + * @return the name of the header {@code ApicurioRegistrySearchOrderBy}. + */ + public String apicurioRegistrySearchOrderBy() { + return "CamelApicurioRegistrySearchOrderBy"; + } } static ApicurioRegistryEndpointBuilder endpointBuilder(String componentName, String path) { class ApicurioRegistryEndpointBuilderImpl extends AbstractEndpointBuilder implements ApicurioRegistryEndpointBuilder, AdvancedApicurioRegistryEndpointBuilder { From 3aada5b8a171cd032f5bc07d18977e104b3b9d1a Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Wed, 14 Oct 2026 14:51:18 +0200 Subject: [PATCH 10/13] CAMEL-24172: Validate credentials, retry missing content once and align Vert.x Rebased on main and regenerated the endpoint URI factory. The endpoint fails to start on missing Basic/OIDC credentials or an unknown authType, closes its WebClient when OAuth2 setup fails, avoids the deprecated OAuth2 flow option and keeps no cookies. A 404 for version content is retried once before the version is skipped with a warning. Unknown ifExists values fail clearly, and the Vert.x client libraries are declared with the Camel Vert.x version. Co-authored-by: GitHub Copilot --- .../docs/apicurio-registry-component.adoc | 7 +- components/camel-apicurio-registry/pom.xml | 12 +++ .../ApicurioRegistryEndpointUriFactory.java | 2 +- .../docs/apicurio-registry-component.adoc | 7 +- .../ApicurioRegistryConsumer.java | 38 +++++--- .../ApicurioRegistryEndpoint.java | 91 +++++++++++++++---- .../ApicurioRegistryProducer.java | 3 +- .../ApicurioRegistryConsumerTest.java | 67 +++++++++++--- .../ApicurioRegistryEndpointTest.java | 52 +++++++++++ .../ApicurioRegistryProducerTest.java | 17 ++++ 10 files changed, 244 insertions(+), 52 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc index 0eb34f17436d6..74eeae9e7c73d 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc @@ -134,8 +134,11 @@ can move to a page that was already read and be skipped. All version states are listed, so `DRAFT` versions are delivered as soon as they appear. If the registry allows changing the content of a version, a version is not delivered again after its -content changes. With `fetchContent=true`, `DISABLED` versions are skipped, as are versions whose -content is no longer available (HTTP 404), because the registry does not return their content. +content changes. With `fetchContent=true`, `DISABLED` versions are skipped, because the registry +does not return their content. If the content of a version is not found (HTTP 404), the consumer +retries it on the next poll, since a registry node with replicated storage may not have the version +yet; if it is still not found, the version is skipped with a warning. Skipped versions are not +delivered again, even if a disabled version is enabled later. If fetching content fails, or the route fails to process a version (including failures handled by the error handler without being rethrown, such as with the default error handler), diff --git a/components/camel-apicurio-registry/pom.xml b/components/camel-apicurio-registry/pom.xml index 50531a424ec23..451457533bc92 100644 --- a/components/camel-apicurio-registry/pom.xml +++ b/components/camel-apicurio-registry/pom.xml @@ -45,6 +45,18 @@ camel-vertx-common + + + io.vertx + vertx-web-client + ${vertx-version} + + + io.vertx + vertx-auth-oauth2 + ${vertx-version} + + io.apicurio diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java index f5f406516a38d..81d7141cc1983 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointUriFactory.java @@ -88,7 +88,7 @@ public String buildUri(String scheme, Map properties, boolean en String syntax = scheme + BASE; String uri = syntax; - Map copy = new HashMap<>(properties); + Map copy = copyParameters(properties); uri = buildPathParameter(syntax, uri, "groupId", null, false, copy); uri = buildPathParameter(syntax, uri, "artifactId", null, false, copy); diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc index 0eb34f17436d6..74eeae9e7c73d 100644 --- a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -134,8 +134,11 @@ can move to a page that was already read and be skipped. All version states are listed, so `DRAFT` versions are delivered as soon as they appear. If the registry allows changing the content of a version, a version is not delivered again after its -content changes. With `fetchContent=true`, `DISABLED` versions are skipped, as are versions whose -content is no longer available (HTTP 404), because the registry does not return their content. +content changes. With `fetchContent=true`, `DISABLED` versions are skipped, because the registry +does not return their content. If the content of a version is not found (HTTP 404), the consumer +retries it on the next poll, since a registry node with replicated storage may not have the version +yet; if it is still not found, the version is skipped with a warning. Skipped versions are not +delivered again, even if a disabled version is enabled later. If fetching content fails, or the route fails to process a version (including failures handled by the error handler without being rethrown, such as with the default error handler), diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java index 0739923303c79..19c6dc5eeaa97 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java @@ -45,6 +45,7 @@ public class ApicurioRegistryConsumer extends ScheduledPollConsumer { private final ApicurioRegistryEndpoint endpoint; private final ApicurioRegistryConfiguration configuration; private volatile Long lastSeenGlobalId; + private Long notFoundGlobalId; public ApicurioRegistryConsumer(ApicurioRegistryEndpoint endpoint, Processor processor, ApicurioRegistryConfiguration configuration) { @@ -64,7 +65,9 @@ protected int poll() throws Exception { Long globalId = version.getGlobalId(); if (configuration.isFetchContent() && version.getState() == VersionState.DISABLED) { // the registry answers 404 for the content of a disabled version, so it can never be delivered - LOG.debug("Skipping disabled artifact version {} (globalId {})", version.getVersion(), globalId); + LOG.info("Skipping disabled artifact version {} (globalId {}): its content cannot be fetched and it will not be" + + " delivered again if it is enabled later", + version.getVersion(), globalId); lastSeenGlobalId = globalId; continue; } @@ -82,11 +85,29 @@ protected int poll() throws Exception { } if (configuration.isFetchContent()) { - byte[] content = fetchContent(client, groupId, artifactId, version); - if (content == null) { + byte[] content; + try { + content = fetchContent(client, groupId, artifactId, version); + } catch (ProblemDetails e) { + if (e.getStatus() == null || e.getStatus() != 404) { + throw e; + } + if (!globalId.equals(notFoundGlobalId)) { + // a 404 is not always final: with replicated storage another registry node may not have + // the version yet, so only give up on it if it is still missing on the next poll + LOG.debug("Content of artifact version {} (globalId {}) not found, retrying on the next poll", + version.getVersion(), globalId); + notFoundGlobalId = globalId; + break; + } + LOG.warn("Skipping artifact version {} (globalId {}) whose content is still not available (HTTP 404)" + + " on a second attempt; it will not be delivered: {}", + version.getVersion(), globalId, e.getDetail()); + notFoundGlobalId = null; lastSeenGlobalId = globalId; continue; } + notFoundGlobalId = null; message.setBody(content); } else { message.setBody(version); @@ -114,23 +135,12 @@ protected int poll() throws Exception { return count; } - /** - * Fetches the content of a version, or returns null if the version no longer has retrievable content (it was - * disabled or deleted after the version list was read). - */ private static byte[] fetchContent( RegistryClient client, String groupId, String artifactId, SearchedVersion version) throws Exception { try (InputStream content = client.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) .versions().byVersionExpression(version.getVersion()).content().get()) { return content.readAllBytes(); - } catch (ProblemDetails e) { - if (e.getStatus() != null && e.getStatus() == 404) { - LOG.debug("Skipping artifact version {} (globalId {}) whose content is no longer available: {}", - version.getVersion(), version.getGlobalId(), e.getDetail()); - return null; - } - throw e; } } diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java index 9a24a16031031..6572a9e7df0eb 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java @@ -18,10 +18,12 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; +import java.util.Collections; import io.apicurio.registry.client.RegistryClientFactory; import io.apicurio.registry.client.common.RegistryClientOptions; import io.apicurio.registry.rest.client.RegistryClient; +import io.netty.handler.codec.http.cookie.Cookie; import io.vertx.core.Vertx; import io.vertx.core.net.ProxyOptions; import io.vertx.ext.auth.oauth2.OAuth2Auth; @@ -32,6 +34,7 @@ import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.client.WebClientOptions; import io.vertx.ext.web.client.WebClientSession; +import io.vertx.ext.web.client.spi.CookieStore; import org.apache.camel.Category; import org.apache.camel.Consumer; import org.apache.camel.Processor; @@ -148,28 +151,82 @@ WebClientOptions createWebClientOptions() throws Exception { return options; } - private WebClient createWebClient(Vertx vertx) throws Exception { - WebClientOptions options = createWebClientOptions(); - WebClient webClient = WebClient.create(vertx, options); + private void validateAuthentication() { String authType = configuration.getAuthType(); + if (ObjectHelper.isEmpty(authType) || "none".equalsIgnoreCase(authType)) { + return; + } if ("basic".equalsIgnoreCase(authType)) { - String credentials = configuration.getUsername() + ":" + configuration.getPassword(); - return WebClientSession.create(webClient).addHeader("Authorization", - "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8))); + requireOption(configuration.getUsername(), "username", authType); + requireOption(configuration.getPassword(), "password", authType); } else if ("oidc".equalsIgnoreCase(authType)) { - oauth2 = OAuth2Auth.create(vertx, new OAuth2Options() - .setFlow(OAuth2FlowType.CLIENT) - .setHttpClientOptions(options) - .setClientId(configuration.getClientId()) - .setClientSecret(configuration.getClientSecret()) - .setTokenPath(configuration.getTokenEndpoint())); - Oauth2Credentials credentials = new Oauth2Credentials(); - if (configuration.getScope() != null) { - credentials.addScope(configuration.getScope()); + requireOption(configuration.getTokenEndpoint(), "tokenEndpoint", authType); + requireOption(configuration.getClientId(), "clientId", authType); + requireOption(configuration.getClientSecret(), "clientSecret", authType); + } else { + throw new IllegalArgumentException( + "Unsupported authType '" + authType + "'. Supported values are none, basic and oidc"); + } + } + + private static void requireOption(String value, String option, String authType) { + if (ObjectHelper.isEmpty(value)) { + throw new IllegalArgumentException("The " + option + " option is required when authType=" + authType); + } + } + + private WebClient createWebClient(Vertx vertx) throws Exception { + validateAuthentication(); + WebClientOptions options = createWebClientOptions(); + WebClient webClient = WebClient.create(vertx, options); + try { + String authType = configuration.getAuthType(); + if ("basic".equalsIgnoreCase(authType)) { + String credentials = configuration.getUsername() + ":" + configuration.getPassword(); + // the session is only used to add the Authorization header: it gets a cookie store that keeps + // nothing, so response cookies are not shared between the requests of this endpoint + return WebClientSession.create(webClient, NoCookieStore.INSTANCE).addHeader("Authorization", + "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8))); + } else if ("oidc".equalsIgnoreCase(authType)) { + oauth2 = OAuth2Auth.create(vertx, new OAuth2Options() + .setHttpClientOptions(options) + .setClientId(configuration.getClientId()) + .setClientSecret(configuration.getClientSecret()) + .setTokenPath(configuration.getTokenEndpoint())); + Oauth2Credentials credentials = new Oauth2Credentials().setFlow(OAuth2FlowType.CLIENT); + if (configuration.getScope() != null) { + credentials.addScope(configuration.getScope()); + } + return OAuth2WebClient.create(webClient, oauth2).withCredentials(credentials); } - return OAuth2WebClient.create(webClient, oauth2).withCredentials(credentials); + return webClient; + } catch (Exception e) { + webClient.close(); + throw e; + } + } + + /** + * A cookie store that never keeps a cookie. + */ + private static final class NoCookieStore implements CookieStore { + + static final NoCookieStore INSTANCE = new NoCookieStore(); + + @Override + public Iterable get(Boolean ssl, String domain, String path) { + return Collections.emptyList(); + } + + @Override + public CookieStore put(Cookie cookie) { + return this; + } + + @Override + public CookieStore remove(Cookie cookie) { + return this; } - return webClient; } public RegistryClient getRegistryClient() { diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java index d574a30ff9827..9f59fdbe73557 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java @@ -121,7 +121,8 @@ public void createArtifact(Message message) { CreateArtifactResponse result = getClient().groups().byGroupId(groupId).artifacts() .post(createArtifact, config -> { if (ifExistsVal != null) { - config.queryParameters.ifExists = IfArtifactExists.forValue(ifExistsVal); + config.queryParameters.ifExists = parseEnum(IfArtifactExists.forValue(ifExistsVal), ifExistsVal, + ApicurioRegistryConstants.HEADER_IF_EXISTS); } }); message.setBody(result); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java index 592eca087b144..5860f066f1a40 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -183,32 +183,69 @@ void testMissingIdentifierFailsWhenCreatingConsumer(String path) throws Exceptio } @Test - void testFetchContentSkipsDisabledAndMissingVersions() throws Exception { + void testFetchContentSkipsDisabledVersions() throws Exception { SearchedVersion disabled = FakeVersionPages.version(2); disabled.setState(VersionState.DISABLED); - pages.add(1).add(disabled).add(3, 4); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().byVersionExpression("1").content().get()) - .thenReturn(new ByteArrayInputStream(new byte[] { 1 })); - // a version disabled or deleted after the list was read: the registry answers 404 for its content - ProblemDetails notFound = new ProblemDetails(); - notFound.setStatus(404); - notFound.setName("VersionNotFoundException"); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().byVersionExpression("3").content().get()).thenThrow(notFound); - when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().byVersionExpression("4").content().get()) - .thenReturn(new ByteArrayInputStream(new byte[] { 4 })); + pages.add(1).add(disabled).add(3); + stubContent("1", new byte[] { 1 }); + stubContent("3", new byte[] { 3 }); List received = new ArrayList<>(); ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI + "&fetchContent=true", received); assertThat(consumer.poll()).isEqualTo(2); - assertThat(globalIds(received)).containsExactly(1L, 4L); + assertThat(globalIds(received)).containsExactly(1L, 3L); verify(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") .versions().byVersionExpression("2").content(), never()).get(); assertThat(consumer.poll()).isZero(); } + @Test + void testContentNotFoundIsRetriedOnceThenSkipped() throws Exception { + pages.add(1, 2); + stubContent("1", new byte[] { 1 }); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("2").content().get()) + .thenThrow(notFound()).thenThrow(notFound()); + List received = new ArrayList<>(); + ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI + "&fetchContent=true", received); + + // the first 404 stops the poll without moving the watermark past version 2 + assertThat(consumer.poll()).isEqualTo(1); + // still not found on the next poll: skipped for good + assertThat(consumer.poll()).isZero(); + pages.add(3); + stubContent("3", new byte[] { 3 }); + assertThat(consumer.poll()).isEqualTo(1); + assertThat(globalIds(received)).containsExactly(1L, 3L); + } + + @Test + void testContentNotFoundOnceIsDeliveredOnNextPoll() throws Exception { + pages.add(1); + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression("1").content().get()) + .thenThrow(notFound()).thenReturn(new ByteArrayInputStream(new byte[] { 1 })); + List received = new ArrayList<>(); + ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI + "&fetchContent=true", received); + + assertThat(consumer.poll()).isZero(); + assertThat(consumer.poll()).isEqualTo(1); + assertThat(received.get(0).getIn().getBody()).isEqualTo(new byte[] { 1 }); + } + + private void stubContent(String version, byte[] content) { + when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") + .versions().byVersionExpression(version).content().get()) + .thenReturn(new ByteArrayInputStream(content)); + } + + private static ProblemDetails notFound() { + ProblemDetails notFound = new ProblemDetails(); + notFound.setStatus(404); + notFound.setName("VersionNotFoundException"); + return notFound; + } + @Test void testOtherContentErrorsAreRetried() throws Exception { pages.add(1); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java index 6f0a074b543ef..7185d9320d612 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java @@ -20,6 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; import com.sun.net.httpserver.HttpServer; @@ -28,16 +29,22 @@ import io.apicurio.registry.rest.client.RegistryClient; import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.ext.auth.oauth2.OAuth2Auth; +import io.vertx.ext.auth.oauth2.OAuth2Options; import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.client.WebClientOptions; +import org.apache.camel.ResolveEndpointFailedException; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.support.jsse.KeyStoreParameters; import org.apache.camel.support.jsse.SSLContextParameters; import org.apache.camel.support.jsse.TrustManagersParameters; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.mockito.MockedStatic; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; @@ -60,6 +67,7 @@ void testOidcAuthentication() throws Exception { private void testAuthentication(String options, String authorization, boolean oidc) throws Exception { AtomicReference registryAuthorization = new AtomicReference<>(); + List registryCookies = new CopyOnWriteArrayList<>(); AtomicReference tokenRequest = new AtomicReference<>(); AtomicReference tokenAuthorization = new AtomicReference<>(); HttpServer server = HttpServer.create(new InetSocketAddress("localhost", 0), 0); @@ -76,6 +84,8 @@ private void testAuthentication(String options, String authorization, boolean oi }); server.createContext("/apis/registry/v3/groups/g/artifacts/a", request -> { registryAuthorization.set(request.getRequestHeaders().getFirst("Authorization")); + registryCookies.add(String.valueOf(request.getRequestHeaders().getFirst("Cookie"))); + request.getResponseHeaders().add("Set-Cookie", "LB=node1; Path=/"); byte[] response = "{\"artifactId\":\"a\",\"groupId\":\"g\"}".getBytes(StandardCharsets.UTF_8); request.getResponseHeaders().set("Content-Type", "application/json"); request.sendResponseHeaders(200, response.length); @@ -95,6 +105,10 @@ private void testAuthentication(String options, String authorization, boolean oi assertThat(endpoint.getRegistryClient().groups().byGroupId("g").artifacts().byArtifactId("a") .get().getArtifactId()).isEqualTo("a"); assertThat(registryAuthorization.get()).isEqualTo(authorization); + // a second request still authenticates, and response cookies are not sent back + endpoint.getRegistryClient().groups().byGroupId("g").artifacts().byArtifactId("a").get(); + assertThat(registryAuthorization.get()).isEqualTo(authorization); + assertThat(registryCookies).containsExactly("null", "null"); if (oidc) { assertThat(tokenRequest.get()).contains("grant_type=client_credentials", "scope=registry"); assertThat(tokenAuthorization.get()).isEqualTo("Basic cmVnaXN0cnktY2xpZW50OnNlY3JldA=="); @@ -233,6 +247,44 @@ void testTlsAndProxyOptions() throws Exception { } } + @ParameterizedTest + @CsvSource(delimiter = '|', value = { + "authType=basic&password=pass|The username option is required when authType=basic", + "authType=basic&username=user|The password option is required when authType=basic", + "authType=oidc&clientId=c&clientSecret=s|The tokenEndpoint option is required when authType=oidc", + "authType=oidc&tokenEndpoint=http://t&clientSecret=s|The clientId option is required when authType=oidc", + "authType=oidc&tokenEndpoint=http://t&clientId=c|The clientSecret option is required when authType=oidc", + "authType=oauth|Unsupported authType 'oauth'. Supported values are none, basic and oidc" }) + void testInvalidAuthenticationFailsOnStart(String options, String message) throws Exception { + try (DefaultCamelContext context = new DefaultCamelContext()) { + context.start(); + // endpoints resolved from a started context are started immediately + assertThatThrownBy(() -> context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3&" + options)) + .isInstanceOf(ResolveEndpointFailedException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasRootCauseMessage(message); + } + } + + @Test + void testWebClientClosedWhenOAuth2SetupFails() throws Exception { + WebClient webClient = mock(WebClient.class); + try (MockedStatic webClientFactory = mockStatic(WebClient.class); + MockedStatic oauth2Factory = mockStatic(OAuth2Auth.class); + DefaultCamelContext context = new DefaultCamelContext()) { + webClientFactory.when(() -> WebClient.create(any(Vertx.class), any(WebClientOptions.class))).thenReturn(webClient); + oauth2Factory.when(() -> OAuth2Auth.create(any(Vertx.class), any(OAuth2Options.class))) + .thenThrow(new IllegalStateException("bad oauth2 configuration")); + context.start(); + assertThatThrownBy(() -> context.getEndpoint( + "apicurio-registry:g/a?registryUrl=http://localhost:8080/apis/registry/v3&authType=oidc" + + "&tokenEndpoint=http://localhost/token&clientId=c&clientSecret=s")) + .hasRootCauseMessage("bad oauth2 configuration"); + verify(webClient).close(); + } + } + @Test void testCallerOwnedClientSurvivesRestart() throws Exception { RegistryClient client = mock(RegistryClient.class); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java index 0b93248496d2f..5681d3d52224f 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java @@ -328,6 +328,23 @@ void testSearchArtifactsRejectsUnknownSort() throws Exception { .hasRootCauseMessage("Unsupported value 'size' for header CamelApicurioRegistrySearchOrderBy"); } + @Test + void testUnknownIfExistsFailsClearly() throws Exception { + injectMockClient( + "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=createArtifact"); + when(mockClient.groups().byGroupId("testGroup").artifacts().post(any(CreateArtifact.class), any())) + .thenAnswer(call -> { + ArtifactsRequestBuilder builder = mockClient.groups().byGroupId("testGroup").artifacts(); + Consumer configurer = call.getArgument(1); + configurer.accept(builder.new PostRequestConfiguration()); + return new CreateArtifactResponse(); + }); + + assertThatThrownBy(() -> template.requestBodyAndHeader("direct:createArtifact", "{}", + ApicurioRegistryConstants.HEADER_IF_EXISTS, "CREATE_VERSIONS")) + .hasRootCauseMessage("Unsupported value 'CREATE_VERSIONS' for header CamelApicurioRegistryIfExists"); + } + @ParameterizedTest @ValueSource(strings = { "getArtifactMetadata", "deleteArtifact", "listVersions", "validate" }) void testMissingArtifactIdFailsClearly(String operation) throws Exception { From 13f0d1d06b6b9f2bae6d14db23de85bf4d9d4d76 Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Wed, 28 Oct 2026 09:45:03 +0100 Subject: [PATCH 11/13] CAMEL-24172: Target 4.24, use SDK 3.3.3 and keep 404 retries across greedy polls Main moved to 4.24.0-SNAPSHOT after 4.23.0 shipped without this component, so the parents, firstVersion and since move to 4.24 and the BOM, catalog, DSL and test-infra metadata are regenerated. The Apicurio SDK and test image move to 3.3.3, and the component now builds and passes on Vert.x 5.2.1. A content 404 is only given up once a polling delay has passed, so greedy polling cannot skip a version on a lagging replica. Co-authored-by: GitHub Copilot --- bom/camel-bom/pom.xml | 2 +- .../catalog/components/apicurio-registry.json | 4 +-- .../docs/apicurio-registry-component.adoc | 7 +++--- .../camel/catalog/test-infra/metadata.json | 4 +-- components/camel-apicurio-registry/pom.xml | 8 +++++- .../apicurioregistry/apicurio-registry.json | 4 +-- .../org/apache/camel/component.properties | 2 +- .../docs/apicurio-registry-component.adoc | 7 +++--- .../ApicurioRegistryConsumer.java | 19 +++++++++----- .../ApicurioRegistryEndpoint.java | 3 ++- .../ApicurioRegistryConsumerTest.java | 25 +++++++++++++------ .../component/ComponentsBuilderFactory.java | 2 +- ...icurioRegistryComponentBuilderFactory.java | 2 +- .../endpoint/EndpointHeaderBuilders.java | 2 +- .../endpoint/StaticEndpointBuilders.java | 4 +-- ...picurioRegistryEndpointBuilderFactory.java | 6 ++--- parent/pom.xml | 2 +- .../resources/META-INF/metadata.json | 4 +-- .../pom.xml | 2 +- .../registry/services/container.properties | 2 +- 20 files changed, 69 insertions(+), 42 deletions(-) diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index f9f72f8e8d2d6..02725fdb6c32d 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -139,7 +139,7 @@ org.apache.camel camel-apicurio-registry - 4.23.0-SNAPSHOT + 4.24.0-SNAPSHOT org.apache.camel diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json index 9b2684a33b96f..a2e2a9aeb014e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/apicurio-registry.json @@ -5,13 +5,13 @@ "title": "Apicurio Registry", "description": "Manage artifacts, versions, and groups in Apicurio Registry v3.", "deprecated": false, - "firstVersion": "4.23.0", + "firstVersion": "4.24.0", "label": "cloud,api", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent", "supportLevel": "Preview", "groupId": "org.apache.camel", "artifactId": "camel-apicurio-registry", - "version": "4.23.0-SNAPSHOT", + "version": "4.24.0-SNAPSHOT", "scheme": "apicurio-registry", "extendsScheme": "", "syntax": "apicurio-registry:groupId\/artifactId", diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc index 74eeae9e7c73d..9eda26ea3da7b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc @@ -3,7 +3,7 @@ :shortname: apicurio-registry :artifactid: camel-apicurio-registry :description: Manage artifacts, versions, and groups in Apicurio Registry v3. -:since: 4.23 +:since: 4.24 :supportlevel: Preview :tabs-sync-option: :component-header: Both producer and consumer are supported @@ -136,8 +136,9 @@ All version states are listed, so `DRAFT` versions are delivered as soon as they registry allows changing the content of a version, a version is not delivered again after its content changes. With `fetchContent=true`, `DISABLED` versions are skipped, because the registry does not return their content. If the content of a version is not found (HTTP 404), the consumer -retries it on the next poll, since a registry node with replicated storage may not have the version -yet; if it is still not found, the version is skipped with a warning. Skipped versions are not +retries it on later polls, since a registry node with replicated storage may not have the version +yet; if it is still not found once at least one polling `delay` has passed (also with `greedy=true`), +the version is skipped with a warning. Skipped versions are not delivered again, even if a disabled version is enabled later. If fetching content fails, or the route fails to process a version (including failures diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json index 190ef16ef2a8b..0979943c1d8d0 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json @@ -600,8 +600,8 @@ "aliasImplementation" : [ ], "groupId" : "org.apache.camel", "artifactId" : "camel-test-infra-apicurio-registry", - "version" : "4.23.0-SNAPSHOT", - "serviceVersion" : "3.3.0", + "version" : "4.24.0-SNAPSHOT", + "serviceVersion" : "3.3.3", "uiSupported" : false }, { "service" : "org.apache.camel.test.infra.postgres.services.PostgresInfraService", diff --git a/components/camel-apicurio-registry/pom.xml b/components/camel-apicurio-registry/pom.xml index 451457533bc92..cdd4da625d43a 100644 --- a/components/camel-apicurio-registry/pom.xml +++ b/components/camel-apicurio-registry/pom.xml @@ -24,7 +24,7 @@ components org.apache.camel - 4.23.0-SNAPSHOT + 4.24.0-SNAPSHOT camel-apicurio-registry @@ -84,6 +84,12 @@ camel-mock test + + org.awaitility + awaitility + ${awaitility-version} + test + org.mockito mockito-core diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json index 9b2684a33b96f..a2e2a9aeb014e 100644 --- a/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/org/apache/camel/component/apicurioregistry/apicurio-registry.json @@ -5,13 +5,13 @@ "title": "Apicurio Registry", "description": "Manage artifacts, versions, and groups in Apicurio Registry v3.", "deprecated": false, - "firstVersion": "4.23.0", + "firstVersion": "4.24.0", "label": "cloud,api", "javaType": "org.apache.camel.component.apicurioregistry.ApicurioRegistryComponent", "supportLevel": "Preview", "groupId": "org.apache.camel", "artifactId": "camel-apicurio-registry", - "version": "4.23.0-SNAPSHOT", + "version": "4.24.0-SNAPSHOT", "scheme": "apicurio-registry", "extendsScheme": "", "syntax": "apicurio-registry:groupId\/artifactId", diff --git a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties index 7036bd840740a..2abaa1e313852 100644 --- a/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties +++ b/components/camel-apicurio-registry/src/generated/resources/META-INF/services/org/apache/camel/component.properties @@ -2,6 +2,6 @@ components=apicurio-registry groupId=org.apache.camel artifactId=camel-apicurio-registry -version=4.23.0-SNAPSHOT +version=4.24.0-SNAPSHOT projectName=Camel :: Apicurio Registry projectDescription=Camel Apicurio Registry component diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc index 74eeae9e7c73d..9eda26ea3da7b 100644 --- a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -3,7 +3,7 @@ :shortname: apicurio-registry :artifactid: camel-apicurio-registry :description: Manage artifacts, versions, and groups in Apicurio Registry v3. -:since: 4.23 +:since: 4.24 :supportlevel: Preview :tabs-sync-option: :component-header: Both producer and consumer are supported @@ -136,8 +136,9 @@ All version states are listed, so `DRAFT` versions are delivered as soon as they registry allows changing the content of a version, a version is not delivered again after its content changes. With `fetchContent=true`, `DISABLED` versions are skipped, because the registry does not return their content. If the content of a version is not found (HTTP 404), the consumer -retries it on the next poll, since a registry node with replicated storage may not have the version -yet; if it is still not found, the version is skipped with a warning. Skipped versions are not +retries it on later polls, since a registry node with replicated storage may not have the version +yet; if it is still not found once at least one polling `delay` has passed (also with `greedy=true`), +the version is skipped with a warning. Skipped versions are not delivered again, even if a disabled version is enabled later. If fetching content fails, or the route fails to process a version (including failures diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java index 19c6dc5eeaa97..81e70b914e13e 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumer.java @@ -45,7 +45,9 @@ public class ApicurioRegistryConsumer extends ScheduledPollConsumer { private final ApicurioRegistryEndpoint endpoint; private final ApicurioRegistryConfiguration configuration; private volatile Long lastSeenGlobalId; + // only accessed from the single poll thread managed by ScheduledPollConsumer, so volatile is not needed private Long notFoundGlobalId; + private long notFoundSince; public ApicurioRegistryConsumer(ApicurioRegistryEndpoint endpoint, Processor processor, ApicurioRegistryConfiguration configuration) { @@ -92,17 +94,22 @@ protected int poll() throws Exception { if (e.getStatus() == null || e.getStatus() != 404) { throw e; } + // a 404 is not always final: with replicated storage another registry node may not have + // the version yet, so only give up on it once at least one polling delay has passed. This + // also holds when greedy polling runs the next poll immediately. + long now = System.currentTimeMillis(); if (!globalId.equals(notFoundGlobalId)) { - // a 404 is not always final: with replicated storage another registry node may not have - // the version yet, so only give up on it if it is still missing on the next poll - LOG.debug("Content of artifact version {} (globalId {}) not found, retrying on the next poll", - version.getVersion(), globalId); notFoundGlobalId = globalId; + notFoundSince = now; + } + if (now - notFoundSince < getDelay()) { + LOG.debug("Content of artifact version {} (globalId {}) not found, retrying on a later poll", + version.getVersion(), globalId); break; } LOG.warn("Skipping artifact version {} (globalId {}) whose content is still not available (HTTP 404)" - + " on a second attempt; it will not be delivered: {}", - version.getVersion(), globalId, e.getDetail()); + + " after {} ms; it will not be delivered: {}", + version.getVersion(), globalId, now - notFoundSince, e.getDetail()); notFoundGlobalId = null; lastSeenGlobalId = globalId; continue; diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java index 6572a9e7df0eb..43109cc2879ef 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java @@ -50,7 +50,7 @@ /** * Manage artifacts, versions, and groups in Apicurio Registry v3. */ -@UriEndpoint(firstVersion = "4.23.0", scheme = "apicurio-registry", title = "Apicurio Registry", +@UriEndpoint(firstVersion = "4.24.0", scheme = "apicurio-registry", title = "Apicurio Registry", syntax = "apicurio-registry:groupId/artifactId", category = { Category.CLOUD, Category.API }, headersClass = ApicurioRegistryConstants.class) public class ApicurioRegistryEndpoint extends ScheduledPollEndpoint implements EndpointServiceLocation { @@ -114,6 +114,7 @@ protected void doStart() throws Exception { @Override protected void doStop() throws Exception { super.doStop(); + // only clean up what this endpoint created; a client set with setRegistryClient() survives a restart if (ownedWebClient != null) { registryClient = null; closeOwnedResources(); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java index 5860f066f1a40..148faafa16745 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryConsumerTest.java @@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -200,23 +201,33 @@ void testFetchContentSkipsDisabledVersions() throws Exception { } @Test - void testContentNotFoundIsRetriedOnceThenSkipped() throws Exception { + void testContentNotFoundIsRetriedUntilDelayPassedThenSkipped() throws Exception { pages.add(1, 2); stubContent("1", new byte[] { 1 }); when(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact") - .versions().byVersionExpression("2").content().get()) - .thenThrow(notFound()).thenThrow(notFound()); + .versions().byVersionExpression("2").content().get()).thenThrow(notFound()); List received = new ArrayList<>(); - ApicurioRegistryConsumer consumer = createConsumer(ENDPOINT_URI + "&fetchContent=true", received); + // a short delay so the test does not wait long before the version is given up + ApicurioRegistryConsumer consumer = createConsumer( + "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3" + + "&delay=200&fetchContent=true", + received); // the first 404 stops the poll without moving the watermark past version 2 assertThat(consumer.poll()).isEqualTo(1); - // still not found on the next poll: skipped for good + // an immediate retry, as with greedy polling, does not give up on the version yet assertThat(consumer.poll()).isZero(); pages.add(3); stubContent("3", new byte[] { 3 }); - assertThat(consumer.poll()).isEqualTo(1); - assertThat(globalIds(received)).containsExactly(1L, 3L); + assertThat(consumer.poll()).isZero(); + assertThat(globalIds(received)).containsExactly(1L); + + // once a polling delay has passed, version 2 is skipped and version 3 is delivered + await().atMost(5, TimeUnit.SECONDS).pollInterval(50, TimeUnit.MILLISECONDS) + .untilAsserted(() -> { + consumer.poll(); + assertThat(globalIds(received)).containsExactly(1L, 3L); + }); } @Test diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java index cc5695bfd6396..e0478d5e9fe3f 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/ComponentsBuilderFactory.java @@ -237,7 +237,7 @@ static AmqpComponentBuilderFactory.AmqpComponentBuilder amqp() { * Manage artifacts, versions, and groups in Apicurio Registry v3. * * Category: cloud,api - * Since: 4.23 + * Since: 4.24 * Maven coordinates: org.apache.camel:camel-apicurio-registry * * @return the dsl builder diff --git a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java index 505549db1bd5d..6806a704e7cd2 100644 --- a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java +++ b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/ApicurioRegistryComponentBuilderFactory.java @@ -36,7 +36,7 @@ public interface ApicurioRegistryComponentBuilderFactory { * Manage artifacts, versions, and groups in Apicurio Registry v3. * * Category: cloud,api - * Since: 4.23 + * Since: 4.24 * Maven coordinates: org.apache.camel:camel-apicurio-registry * * @return the dsl builder diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java index 1a8e2b75aef4b..4f0d3b261e86e 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/EndpointHeaderBuilders.java @@ -216,7 +216,7 @@ public static AMQPEndpointBuilderFactory.AMQPHeaderNameBuilder amqp() { * Manage artifacts, versions, and groups in Apicurio Registry v3. * * Category: cloud,api - * Since: 4.23 + * Since: 4.24 * Maven coordinates: org.apache.camel:camel-apicurio-registry * * @return the dsl builder for the headers' name. diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java index c7d04305be830..dc3ca6fd34ced 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/StaticEndpointBuilders.java @@ -661,7 +661,7 @@ public static AMQPEndpointBuilderFactory.AMQPEndpointBuilder amqp(String compone * Manage artifacts, versions, and groups in Apicurio Registry v3. * * Category: cloud,api - * Since: 4.23 + * Since: 4.24 * Maven coordinates: org.apache.camel:camel-apicurio-registry * * Syntax: apicurio-registry:groupId/artifactId @@ -683,7 +683,7 @@ public static ApicurioRegistryEndpointBuilderFactory.ApicurioRegistryEndpointBui * Manage artifacts, versions, and groups in Apicurio Registry v3. * * Category: cloud,api - * Since: 4.23 + * Since: 4.24 * Maven coordinates: org.apache.camel:camel-apicurio-registry * * Syntax: apicurio-registry:groupId/artifactId diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java index b97a2443b9e79..5d513694366a6 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/ApicurioRegistryEndpointBuilderFactory.java @@ -1653,7 +1653,7 @@ public interface ApicurioRegistryBuilders { * Manage artifacts, versions, and groups in Apicurio Registry v3. * * Category: cloud,api - * Since: 4.23 + * Since: 4.24 * Maven coordinates: org.apache.camel:camel-apicurio-registry * * @return the dsl builder for the headers' name. @@ -1666,7 +1666,7 @@ default ApicurioRegistryHeaderNameBuilder apicurioRegistry() { * Manage artifacts, versions, and groups in Apicurio Registry v3. * * Category: cloud,api - * Since: 4.23 + * Since: 4.24 * Maven coordinates: org.apache.camel:camel-apicurio-registry * * Syntax: apicurio-registry:groupId/artifactId @@ -1688,7 +1688,7 @@ default ApicurioRegistryEndpointBuilder apicurioRegistry(String path) { * Manage artifacts, versions, and groups in Apicurio Registry v3. * * Category: cloud,api - * Since: 4.23 + * Since: 4.24 * Maven coordinates: org.apache.camel:camel-apicurio-registry * * Syntax: apicurio-registry:groupId/artifactId diff --git a/parent/pom.xml b/parent/pom.xml index d97283366e456..0aa18dc4cf8f5 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -79,7 +79,7 @@ 2.0.5 2.0.0.AM27 1.22.0 - 3.3.0 + 3.3.3 7.28.0 1.13.0 9.11.1 diff --git a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json index 190ef16ef2a8b..0979943c1d8d0 100644 --- a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json +++ b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json @@ -600,8 +600,8 @@ "aliasImplementation" : [ ], "groupId" : "org.apache.camel", "artifactId" : "camel-test-infra-apicurio-registry", - "version" : "4.23.0-SNAPSHOT", - "serviceVersion" : "3.3.0", + "version" : "4.24.0-SNAPSHOT", + "serviceVersion" : "3.3.3", "uiSupported" : false }, { "service" : "org.apache.camel.test.infra.postgres.services.PostgresInfraService", diff --git a/test-infra/camel-test-infra-apicurio-registry/pom.xml b/test-infra/camel-test-infra-apicurio-registry/pom.xml index 0151eaab6a59e..2280ed1a54e7b 100644 --- a/test-infra/camel-test-infra-apicurio-registry/pom.xml +++ b/test-infra/camel-test-infra-apicurio-registry/pom.xml @@ -22,7 +22,7 @@ camel-test-infra-parent org.apache.camel ../camel-test-infra-parent/pom.xml - 4.23.0-SNAPSHOT + 4.24.0-SNAPSHOT 4.0.0 diff --git a/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties b/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties index a153fd2fe64c6..52fe8f7536525 100644 --- a/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties +++ b/test-infra/camel-test-infra-apicurio-registry/src/main/resources/org/apache/camel/test/infra/apicurio/registry/services/container.properties @@ -14,4 +14,4 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## --------------------------------------------------------------------------- -apicurio.registry.container=quay.io/apicurio/apicurio-registry:3.3.0 +apicurio.registry.container=quay.io/apicurio/apicurio-registry:3.3.3 From 18614d222a33f0ae91dc4f3e4cd8b970e696a945 Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Wed, 28 Oct 2026 10:25:18 +0100 Subject: [PATCH 12/13] CAMEL-24172: Do not follow redirects, guard the event loop and detect YAML content The SDK Vert.x adapter enables redirect following on every request, so the endpoint builds its HTTP client with a redirect handler that never follows one; credentials are no longer sent to a redirect target. The producer fails fast on a Vert.x event loop thread instead of hanging. Without a content type header, the content type is derived from the content, so OpenAPI and AsyncAPI YAML documents are no longer sent as JSON. Co-authored-by: GitHub Copilot --- .../docs/apicurio-registry-component.adoc | 11 ++ .../docs/apicurio-registry-component.adoc | 11 ++ .../ApicurioRegistryEndpoint.java | 16 +- .../ApicurioRegistryProducer.java | 45 ++++- .../ApicurioRegistryEndpointTest.java | 86 +++++++++- .../ApicurioRegistryProducerTest.java | 42 +++++ .../ApicurioRegistryArtifactTypesIT.java | 154 ++++++++++++++++++ 7 files changed, 349 insertions(+), 16 deletions(-) create mode 100644 components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryArtifactTypesIT.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc index 9eda26ea3da7b..caab1458777c1 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/apicurio-registry-component.adoc @@ -73,6 +73,10 @@ the `CamelApicurioRegistryOperation` header: Artifact content supplied to `createArtifact`, `updateArtifact`, `validate`, and `testCompatibility` is converted to a string for the SDK's `VersionContent` model. +Its content type is taken from the `CamelApicurioRegistryContentType` header. Without the header, it is +derived from the content: `application/json` when it starts with `{` or `[`, `application/xml` when it +starts with `<`, `application/x-protobuf` or `application/graphql` for PROTOBUF and GRAPHQL artifacts, +and `application/x-yaml` otherwise (for example OpenAPI or AsyncAPI documents written in YAML). For AVRO and PROTOBUF, supply the schema definition, rather than serialized message data. `getArtifactContent` returns a `byte[]`; the component closes the SDK response stream before passing the content to the route. @@ -274,6 +278,13 @@ through an HTTP proxy. Both settings also apply to the OAuth2 token endpoint. == Authentication +The component never follows HTTP redirects, so credentials are never sent to another host. Configure +`registryUrl` with the final address of the registry API. + +The producer makes blocking calls. It cannot run on a Vert.x event loop thread, for example in a route +started by an event-loop consumer of the same Vert.x instance; it fails with an `IllegalStateException` +there. Hand the exchange to a worker thread first, for example with `threads()`. + The component supports three authentication modes via the `authType` option: * `none` (default): No authentication. diff --git a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc index 9eda26ea3da7b..caab1458777c1 100644 --- a/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc +++ b/components/camel-apicurio-registry/src/main/docs/apicurio-registry-component.adoc @@ -73,6 +73,10 @@ the `CamelApicurioRegistryOperation` header: Artifact content supplied to `createArtifact`, `updateArtifact`, `validate`, and `testCompatibility` is converted to a string for the SDK's `VersionContent` model. +Its content type is taken from the `CamelApicurioRegistryContentType` header. Without the header, it is +derived from the content: `application/json` when it starts with `{` or `[`, `application/xml` when it +starts with `<`, `application/x-protobuf` or `application/graphql` for PROTOBUF and GRAPHQL artifacts, +and `application/x-yaml` otherwise (for example OpenAPI or AsyncAPI documents written in YAML). For AVRO and PROTOBUF, supply the schema definition, rather than serialized message data. `getArtifactContent` returns a `byte[]`; the component closes the SDK response stream before passing the content to the route. @@ -274,6 +278,13 @@ through an HTTP proxy. Both settings also apply to the OAuth2 token endpoint. == Authentication +The component never follows HTTP redirects, so credentials are never sent to another host. Configure +`registryUrl` with the final address of the registry API. + +The producer makes blocking calls. It cannot run on a Vert.x event loop thread, for example in a route +started by an event-loop consumer of the same Vert.x instance; it fails with an `IllegalStateException` +there. Hand the exchange to a worker thread first, for example with `threads()`. + The component supports three authentication modes via the `authType` option: * `none` (default): No authentication. diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java index 43109cc2879ef..0b2d6bf724a60 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpoint.java @@ -25,6 +25,7 @@ import io.apicurio.registry.rest.client.RegistryClient; import io.netty.handler.codec.http.cookie.Cookie; import io.vertx.core.Vertx; +import io.vertx.core.http.HttpClient; import io.vertx.core.net.ProxyOptions; import io.vertx.ext.auth.oauth2.OAuth2Auth; import io.vertx.ext.auth.oauth2.OAuth2FlowType; @@ -68,6 +69,7 @@ public class ApicurioRegistryEndpoint extends ScheduledPollEndpoint implements E private RegistryClient registryClient; private WebClient ownedWebClient; + private HttpClient ownedHttpClient; private OAuth2Auth oauth2; ApicurioRegistryEndpoint(String uri, ApicurioRegistryComponent component, @@ -127,6 +129,10 @@ private void closeOwnedResources() { ownedWebClient.close(); ownedWebClient = null; } + if (ownedHttpClient != null) { + ownedHttpClient.close(); + ownedHttpClient = null; + } if (oauth2 != null) { oauth2.close(); oauth2 = null; @@ -134,7 +140,7 @@ private void closeOwnedResources() { } WebClientOptions createWebClientOptions() throws Exception { - WebClientOptions options = new WebClientOptions(); + WebClientOptions options = new WebClientOptions().setFollowRedirects(false); if (configuration.getSslContextParameters() != null) { VertxHelper.setupSSLOptions(getCamelContext(), configuration.getSslContextParameters(), options); } @@ -179,7 +185,11 @@ private static void requireOption(String value, String option, String authType) private WebClient createWebClient(Vertx vertx) throws Exception { validateAuthentication(); WebClientOptions options = createWebClientOptions(); - WebClient webClient = WebClient.create(vertx, options); + // The SDK's Vert.x adapter turns redirect following on for every request, so the option above is not enough. + // The client adds credentials (Basic header or OAuth2 token) to every request, and following a redirect to + // another host would send them there, so the redirect handler of the underlying client never follows one. + ownedHttpClient = vertx.httpClientBuilder().with(options).withRedirectHandler(response -> null).build(); + WebClient webClient = WebClient.wrap(ownedHttpClient, options); try { String authType = configuration.getAuthType(); if ("basic".equalsIgnoreCase(authType)) { @@ -203,6 +213,8 @@ private WebClient createWebClient(Vertx vertx) throws Exception { return webClient; } catch (Exception e) { webClient.close(); + ownedHttpClient.close(); + ownedHttpClient = null; throw e; } } diff --git a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java index 9f59fdbe73557..b9de5239660f8 100644 --- a/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java +++ b/components/camel-apicurio-registry/src/main/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducer.java @@ -39,6 +39,7 @@ import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; import io.apicurio.registry.rest.client.models.VersionSortBy; +import io.vertx.core.Context; import org.apache.camel.Message; import org.apache.camel.spi.InvokeOnHeader; import org.apache.camel.support.HeaderSelectorProducer; @@ -59,9 +60,44 @@ public ApicurioRegistryProducer(ApicurioRegistryEndpoint endpoint, } private RegistryClient getClient() { + if (Context.isOnEventLoopThread()) { + // the SDK blocks until the response arrives; on an event loop of the Vert.x the client uses, the response + // could never be handled and the call would hang + throw new IllegalStateException( + "The apicurio-registry producer makes blocking calls and cannot run on a Vert.x event loop thread." + + " Hand the exchange to a worker thread first, for example with threads()"); + } return endpoint.getRegistryClient(); } + /** + * The content type from the header, or one derived from the content: JSON or XML when the content starts like it, + * the registry's own types for Protobuf and GraphQL, and YAML otherwise (for example OpenAPI or AsyncAPI documents + * written in YAML). + */ + private String resolveContentType(Message message, String content) { + String contentType = message.getHeader(ApicurioRegistryConstants.HEADER_CONTENT_TYPE, String.class); + if (contentType != null) { + return contentType; + } + String trimmed = content != null ? content.stripLeading() : ""; + if (trimmed.startsWith("{") || trimmed.startsWith("[")) { + return "application/json"; + } + if (trimmed.startsWith("<")) { + return "application/xml"; + } + String artifactType = message.getHeader( + ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, configuration.getArtifactType(), String.class); + if ("PROTOBUF".equalsIgnoreCase(artifactType)) { + return "application/x-protobuf"; + } + if ("GRAPHQL".equalsIgnoreCase(artifactType)) { + return "application/graphql"; + } + return "application/x-yaml"; + } + private String resolveGroupId(Message message) { String gid = message.getHeader(ApicurioRegistryConstants.HEADER_GROUP_ID, String.class); return gid != null ? gid : endpoint.getGroupId(); @@ -98,8 +134,7 @@ public void createArtifact(Message message) { String name = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_NAME, String.class); String description = message.getHeader(ApicurioRegistryConstants.HEADER_ARTIFACT_DESCRIPTION, String.class); String content = message.getBody(String.class); - String contentType = message.getHeader( - ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); + String contentType = resolveContentType(message, content); String ifExistsVal = message.getHeader( ApicurioRegistryConstants.HEADER_IF_EXISTS, configuration.getIfExists(), String.class); @@ -134,8 +169,7 @@ public void updateArtifact(Message message) { String artifactId = requireArtifactId(message, "updateArtifact"); String content = message.getBody(String.class); String version = message.getHeader(ApicurioRegistryConstants.HEADER_VERSION, String.class); - String contentType = message.getHeader( - ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); + String contentType = resolveContentType(message, content); CreateVersion createVersion = new CreateVersion(); createVersion.setVersion(version); @@ -311,8 +345,7 @@ private boolean doDryRun(Message message, String operation) throws Exception { String groupId = requireGroupId(message, operation); String artifactId = requireArtifactId(message, operation); String content = message.getBody(String.class); - String contentType = message.getHeader( - ApicurioRegistryConstants.HEADER_CONTENT_TYPE, "application/json", String.class); + String contentType = resolveContentType(message, content); CreateVersion createVersion = new CreateVersion(); VersionContent vc = new VersionContent(); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java index 7185d9320d612..21f14236beeb7 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryEndpointTest.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import com.sun.net.httpserver.HttpServer; @@ -29,6 +30,9 @@ import io.apicurio.registry.rest.client.RegistryClient; import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.http.HttpClient; +import io.vertx.core.http.HttpClientAgent; +import io.vertx.core.http.HttpClientBuilder; import io.vertx.ext.auth.oauth2.OAuth2Auth; import io.vertx.ext.auth.oauth2.OAuth2Options; import io.vertx.ext.web.client.WebClient; @@ -41,11 +45,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.MockedStatic; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.RETURNS_SELF; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; @@ -123,7 +129,7 @@ private void testAuthentication(String options, String authorization, boolean oi @Test void testEndpointsShareComponentVertxAndCloseTheirWebClients() throws Exception { - Vertx vertx = mock(Vertx.class); + Vertx vertx = mockVertx(); when(vertx.close()).thenReturn(Future.succeededFuture()); List clientVertx = new ArrayList<>(); List clientWebClients = new ArrayList<>(); @@ -133,8 +139,7 @@ void testEndpointsShareComponentVertxAndCloseTheirWebClients() throws Exception MockedStatic clientFactory = mockStatic(RegistryClientFactory.class); DefaultCamelContext context = new DefaultCamelContext()) { vertxFactory.when(Vertx::vertx).thenReturn(vertx); - webClientFactory.when(() -> WebClient.create(any(Vertx.class), any(WebClientOptions.class))).thenAnswer(call -> { - assertThat(call.getArgument(0, Vertx.class)).isSameAs(vertx); + webClientFactory.when(() -> WebClient.wrap(any(HttpClient.class), any(WebClientOptions.class))).thenAnswer(call -> { WebClient webClient = mock(WebClient.class); created.add(webClient); return webClient; @@ -177,12 +182,12 @@ void testEndpointsShareComponentVertxAndCloseTheirWebClients() throws Exception @Test void testProvidedVertxIsNotClosed() throws Exception { - Vertx vertx = mock(Vertx.class); + Vertx vertx = mockVertx(); try (MockedStatic vertxFactory = mockStatic(Vertx.class); MockedStatic webClientFactory = mockStatic(WebClient.class); MockedStatic clientFactory = mockStatic(RegistryClientFactory.class); DefaultCamelContext context = new DefaultCamelContext()) { - webClientFactory.when(() -> WebClient.create(any(Vertx.class), any(WebClientOptions.class))) + webClientFactory.when(() -> WebClient.wrap(any(HttpClient.class), any(WebClientOptions.class))) .thenAnswer(call -> mock(WebClient.class)); clientFactory.when(() -> RegistryClientFactory.create(any())).thenAnswer(call -> { assertThat(call.getArgument(0, RegistryClientOptions.class).getVertx()).isSameAs(vertx); @@ -201,9 +206,9 @@ void testProvidedVertxIsNotClosed() throws Exception { @Test void testVertxSetAfterComponentCreatedOneIsNotClosed() throws Exception { - Vertx managed = mock(Vertx.class); + Vertx managed = mockVertx(); when(managed.close()).thenReturn(Future.succeededFuture()); - Vertx provided = mock(Vertx.class); + Vertx provided = mockVertx(); try (MockedStatic vertxFactory = mockStatic(Vertx.class); DefaultCamelContext context = new DefaultCamelContext()) { vertxFactory.when(Vertx::vertx).thenReturn(managed); @@ -273,7 +278,8 @@ void testWebClientClosedWhenOAuth2SetupFails() throws Exception { try (MockedStatic webClientFactory = mockStatic(WebClient.class); MockedStatic oauth2Factory = mockStatic(OAuth2Auth.class); DefaultCamelContext context = new DefaultCamelContext()) { - webClientFactory.when(() -> WebClient.create(any(Vertx.class), any(WebClientOptions.class))).thenReturn(webClient); + webClientFactory.when(() -> WebClient.wrap(any(HttpClient.class), any(WebClientOptions.class))) + .thenReturn(webClient); oauth2Factory.when(() -> OAuth2Auth.create(any(Vertx.class), any(OAuth2Options.class))) .thenThrow(new IllegalStateException("bad oauth2 configuration")); context.start(); @@ -285,6 +291,70 @@ void testWebClientClosedWhenOAuth2SetupFails() throws Exception { } } + @ParameterizedTest + @ValueSource(strings = { "basic&username=user&password=pass", "oidc&clientId=c&clientSecret=s" }) + void testCredentialsAreNotSentToRedirectTarget(String auth) throws Exception { + List otherHostRequests = new CopyOnWriteArrayList<>(); + HttpServer otherHost = HttpServer.create(new InetSocketAddress("localhost", 0), 0); + otherHost.createContext("/", request -> { + otherHostRequests.add(String.valueOf(request.getRequestHeaders().getFirst("Authorization"))); + request.sendResponseHeaders(200, -1); + request.close(); + }); + HttpServer registry = HttpServer.create(new InetSocketAddress("localhost", 0), 0); + registry.createContext("/token", request -> { + byte[] response = "{\"access_token\":\"t\",\"token_type\":\"Bearer\",\"expires_in\":300}" + .getBytes(StandardCharsets.UTF_8); + request.getResponseHeaders().set("Content-Type", "application/json"); + request.sendResponseHeaders(200, response.length); + try (var body = request.getResponseBody()) { + body.write(response); + } + }); + registry.createContext("/apis", request -> { + request.getResponseHeaders().set("Location", + "http://localhost:" + otherHost.getAddress().getPort() + request.getRequestURI()); + request.sendResponseHeaders(302, -1); + request.close(); + }); + otherHost.start(); + registry.start(); + try (DefaultCamelContext context = new DefaultCamelContext()) { + String baseUrl = "http://localhost:" + registry.getAddress().getPort(); + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:g/a?registryUrl=" + baseUrl + "/apis/registry/v3&tokenEndpoint=" + baseUrl + + "/token&authType=" + auth, + ApicurioRegistryEndpoint.class); + endpoint.start(); + try { + AtomicInteger redirects = new AtomicInteger(); + try { + endpoint.getRegistryClient().groups().byGroupId("g").artifacts().byArtifactId("a").get(); + } catch (Exception e) { + // the unfollowed redirect may surface as an error; either way it must not reach the other host + redirects.incrementAndGet(); + } + assertThat(otherHostRequests).isEmpty(); + } finally { + endpoint.stop(); + } + } finally { + registry.stop(0); + otherHost.stop(0); + } + } + + /** + * A mocked Vert.x whose HTTP client builder returns a mocked client, for tests that also mock WebClient.wrap. + */ + private static Vertx mockVertx() { + Vertx vertx = mock(Vertx.class); + HttpClientBuilder builder = mock(HttpClientBuilder.class, RETURNS_SELF); + when(builder.build()).thenAnswer(call -> mock(HttpClientAgent.class)); + when(vertx.httpClientBuilder()).thenReturn(builder); + return vertx; + } + @Test void testCallerOwnedClientSurvivesRestart() throws Exception { RegistryClient client = mock(RegistryClient.class); diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java index 5681d3d52224f..31fa5424d24d2 100644 --- a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerTest.java @@ -20,6 +20,8 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.LongStream; @@ -41,13 +43,16 @@ import io.apicurio.registry.rest.client.models.SortOrder; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; +import io.vertx.core.Vertx; import org.apache.camel.BindToRegistry; import org.apache.camel.CamelExecutionException; +import org.apache.camel.Exchange; import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.test.junit6.CamelTestSupport; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; @@ -345,6 +350,43 @@ void testUnknownIfExistsFailsClearly() throws Exception { .hasRootCauseMessage("Unsupported value 'CREATE_VERSIONS' for header CamelApicurioRegistryIfExists"); } + @ParameterizedTest + @CsvSource(delimiter = '|', value = { + "{\"type\":\"object\"}|JSON|application/json", + " [1]|JSON|application/json", + "|XSD|application/xml", + "syntax = \"proto3\";|PROTOBUF|application/x-protobuf", + "type Query { a: Int }|GRAPHQL|application/graphql", + "openapi: 3.0.3|OPENAPI|application/x-yaml", + "asyncapi: 2.6.0|ASYNCAPI|application/x-yaml" }) + void testContentTypeDerivedFromContent(String content, String artifactType, String expected) throws Exception { + injectMockClient( + "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=updateArtifact"); + template.requestBodyAndHeader("direct:updateArtifact", content, ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, + artifactType); + ArgumentCaptor request = ArgumentCaptor.forClass(CreateVersion.class); + verify(mockClient.groups().byGroupId("testGroup").artifacts().byArtifactId("testArtifact").versions()) + .post(request.capture()); + assertThat(request.getValue().getContent().getContentType()).isEqualTo(expected); + } + + @Test + void testProducerOnVertxEventLoopFailsFast() throws Exception { + injectMockClient( + "apicurio-registry:testGroup/testArtifact?registryUrl=http://localhost:8080/apis/registry/v3&operation=getArtifactMetadata"); + Vertx vertx = Vertx.vertx(); + try { + CompletableFuture result = new CompletableFuture<>(); + vertx.runOnContext(v -> result.complete(template.request("direct:getArtifactMetadata", exchange -> { + }))); + Exchange exchange = result.get(10, TimeUnit.SECONDS); + assertThat(exchange.getException()).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("cannot run on a Vert.x event loop thread"); + } finally { + vertx.close().toCompletionStage().toCompletableFuture().get(10, TimeUnit.SECONDS); + } + } + @ParameterizedTest @ValueSource(strings = { "getArtifactMetadata", "deleteArtifact", "listVersions", "validate" }) void testMissingArtifactIdFailsClearly(String operation) throws Exception { diff --git a/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryArtifactTypesIT.java b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryArtifactTypesIT.java new file mode 100644 index 0000000000000..cfcc3f8592249 --- /dev/null +++ b/components/camel-apicurio-registry/src/test/java/org/apache/camel/component/apicurioregistry/integration/ApicurioRegistryArtifactTypesIT.java @@ -0,0 +1,154 @@ +/* + * 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.apicurioregistry.integration; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import io.apicurio.registry.rest.client.models.CreateRule; +import io.apicurio.registry.rest.client.models.RuleType; +import org.apache.camel.Exchange; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryConstants; +import org.apache.camel.component.apicurioregistry.ApicurioRegistryEndpoint; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Artifacts of several types created, updated and validated without a content type header, in a group with a FULL + * validity rule, so the registry rejects content sent with the wrong content type. + */ +class ApicurioRegistryArtifactTypesIT extends ApicurioRegistryTestSupport { + + private static final String OPENAPI_V1 = """ + openapi: 3.0.3 + info: + title: Orders + version: "1" + paths: {} + """; + + private static final String OPENAPI_V2 = """ + openapi: 3.0.3 + info: + title: Orders + version: "2" + paths: {} + """; + + private static final String ASYNCAPI = """ + asyncapi: 2.6.0 + info: + title: Events + version: "1" + channels: {} + """; + + private static final String PROTOBUF = """ + syntax = "proto3"; + message Order { + string id = 1; + } + """; + + private static final String GRAPHQL = """ + type Query { + order(id: ID!): String + } + """; + + private static final String XSD = """ + + + + + """; + + private static final Map CONTENT = Map.of( + "OPENAPI", new String[] { OPENAPI_V1, OPENAPI_V2 }, + "ASYNCAPI", new String[] { ASYNCAPI, ASYNCAPI.replace("version: \"1\"", "version: \"2\"") }, + "PROTOBUF", new String[] { PROTOBUF, PROTOBUF.replace("string id = 1;", "string id = 1;\n int32 qty = 2;") }, + "GRAPHQL", new String[] { GRAPHQL, GRAPHQL.replace("String", "Int") }, + "XSD", new String[] { XSD, XSD.replace("xs:string", "xs:int") }); + + private final String groupId = "types-group-" + UUID.randomUUID(); + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:operation") + .toD("apicurio-registry:${header.CamelApicurioRegistryGroupId}/${header.CamelApicurioRegistryArtifactId}" + + "?registryUrl=" + getRegistryUrl()); + } + }; + } + + @BeforeEach + void createGroupWithValidityRule() { + Exchange group = operation(ApicurioRegistryConstants.OPERATION_CREATE_GROUP, null, null, null); + assertThat(group.getException()).isNull(); + ApicurioRegistryEndpoint endpoint = context.getEndpoint( + "apicurio-registry:" + groupId + "?registryUrl=" + getRegistryUrl(), ApicurioRegistryEndpoint.class); + CreateRule rule = new CreateRule(); + rule.setRuleType(RuleType.VALIDITY); + rule.setConfig("FULL"); + endpoint.getRegistryClient().groups().byGroupId(groupId).rules().post(rule); + } + + @ParameterizedTest + @CsvSource({ "OPENAPI", "ASYNCAPI", "PROTOBUF", "GRAPHQL", "XSD" }) + void testCreateUpdateAndValidateWithoutContentTypeHeader(String artifactType) { + String artifactId = artifactType.toLowerCase() + "-" + UUID.randomUUID(); + String[] content = CONTENT.get(artifactType); + + Exchange created = operation(ApicurioRegistryConstants.OPERATION_CREATE_ARTIFACT, artifactId, artifactType, + content[0]); + assertThat(created.getException()).isNull(); + + Exchange updated = operation(ApicurioRegistryConstants.OPERATION_UPDATE_ARTIFACT, artifactId, artifactType, + content[1]); + assertThat(updated.getException()).isNull(); + + Exchange validated = operation(ApicurioRegistryConstants.OPERATION_VALIDATE, artifactId, artifactType, + content[1]); + assertThat(validated.getException()).isNull(); + assertThat(validated.getIn().getHeader(ApicurioRegistryConstants.HEADER_VALIDATION_RESULT)).isEqualTo(true); + } + + private Exchange operation(String operation, String artifactId, String artifactType, String body) { + Map headers = new HashMap<>(); + headers.put(ApicurioRegistryConstants.HEADER_OPERATION, operation); + headers.put(ApicurioRegistryConstants.HEADER_GROUP_ID, groupId); + if (artifactId != null) { + headers.put(ApicurioRegistryConstants.HEADER_ARTIFACT_ID, artifactId); + } + if (artifactType != null) { + headers.put(ApicurioRegistryConstants.HEADER_ARTIFACT_TYPE, artifactType); + } + return template.request("direct:operation", exchange -> { + exchange.getIn().setHeaders(headers); + exchange.getIn().setBody(body); + }); + } +} From c26053be8e60c32bcd14c043d98976645ff162fa Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 28 Oct 2026 09:55:31 +0000 Subject: [PATCH 13/13] Regen --- .../ApicurioRegistryProducerInvokeOnHeaderFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerInvokeOnHeaderFactory.java b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerInvokeOnHeaderFactory.java index 4135109707e99..265b89d29e472 100644 --- a/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerInvokeOnHeaderFactory.java +++ b/components/camel-apicurio-registry/src/generated/java/org/apache/camel/component/apicurioregistry/ApicurioRegistryProducerInvokeOnHeaderFactory.java @@ -38,7 +38,7 @@ public Object invoke(Object obj, String key, Exchange exchange, AsyncCallback ca case "updateartifact": case "updateArtifact": target.updateArtifact(exchange.getMessage()); return null; case "validate": target.validate(exchange.getMessage()); return null; - default: return null; + default: return InvokeOnHeaderStrategy.NO_METHOD; } }