You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2022/03/14 13:37:30 UTC
[camel] branch main updated: CAMEL-16628: Allow to generate doc about message headers (#7190)
This is an automated email from the ASF dual-hosted git repository.
nfilotto pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 5072d09 CAMEL-16628: Allow to generate doc about message headers (#7190)
5072d09 is described below
commit 5072d09284657bdeb3607ee497ad9f7e9d472104
Author: Nicolas Filotto <es...@users.noreply.github.com>
AuthorDate: Mon Mar 14 14:36:55 2022 +0100
CAMEL-16628: Allow to generate doc about message headers (#7190)
## Motivation
Like we have for endpoint options with @UriParam, we should have a new SPI annotation to markup headers that consumer/producers support.
## Modifications
This PR adds a new element called `headersClass` to the annotation `@UriEndpoint` to provide the class that contains the constants corresponding to the name of the headers supported by the consumers/producers.
In the example below, the endpoint `FooEndpoint` indicates that the class that contains the name of all the headers supported by the consumers/producers is `FooConstants`.
```
@UriEndpoint(scheme = "foo", syntax = "foo", title = "foo", headersClass = FooConstants.class)
public class FooEndpoint extends Endpoint {
// Rest of the class
}
```
A headers class can be any class but by convention, we expect a name of type `xxxConstants` where `xxx` is the name of the corresponding component like for example `FtpConstants` for the component `camel-ftp`.
The metadata of a given header are retrieved from the annotation `@Metadata` added to the `String` constant representing its name.
In the example below, the header `FooConstants.FILE_NAME` specify a description, a default value and a java type.
```
public final class FooConstants {
@Metadata(description = "Some description of CamelFileName", defaultValue = "Some default value",
javaType = "java.lang.String")
public static final String FILE_NAME = "CamelFileName";
// Rest of the class
}
```
This PR adds a new asciidoctor template called `component-endpoint-headers.adoc` that can be called from a doc file with:
```
// component headers: START
include::partial$component-endpoint-headers.adoc[]
// component headers: END
```
---
.../java/org/apache/camel/spi/UriEndpoint.java | 12 +++
.../ROOT/partials/component-endpoint-headers.adoc | 24 +++++
.../apache/camel/tooling/model/ComponentModel.java | 13 +++
.../org/apache/camel/tooling/model/JsonMapper.java | 14 +++
.../apache/camel/tooling/model/JsonMapperTest.java | 77 ++++++++++++++
tooling/maven/camel-package-maven-plugin/pom.xml | 52 +++++++++
.../src/it/HeaderSupport/pom.xml | 70 ++++++++++++
.../apache/camel/component/foo/FooComponent.java | 26 +++++
.../apache/camel/component/foo/FooConstants.java | 28 +++++
.../apache/camel/component/foo/FooEndpoint.java | 70 ++++++++++++
.../src/it/HeaderSupport/verify.groovy | 26 +++++
.../camel-package-maven-plugin/src/it/settings.xml | 53 ++++++++++
.../packaging/EndpointSchemaGeneratorMojo.java | 117 ++++++++++++++++++---
.../packaging/EndpointSchemaGeneratorMojoTest.java | 103 ++++++++++++++++++
.../maven/packaging/endpoint/SomeConstants.java | 32 ++++++
.../maven/packaging/endpoint/SomeEndpoint.java | 46 ++++++++
.../endpoint/SomeEndpointWithBadHeaders.java | 33 ++++++
.../endpoint/SomeEndpointWithoutHeaders.java | 23 ++++
.../java/org/apache/camel/spi/UriEndpoint.java | 12 +++
19 files changed, 817 insertions(+), 14 deletions(-)
diff --git a/core/camel-api/src/generated/java/org/apache/camel/spi/UriEndpoint.java b/core/camel-api/src/generated/java/org/apache/camel/spi/UriEndpoint.java
index a3e5a64..d92ea81 100644
--- a/core/camel-api/src/generated/java/org/apache/camel/spi/UriEndpoint.java
+++ b/core/camel-api/src/generated/java/org/apache/camel/spi/UriEndpoint.java
@@ -175,4 +175,16 @@ public @interface UriEndpoint {
*/
String apiSyntax() default "";
+ /**
+ * The class that contains all the name of headers that are supported by the consumer and/or producer. The name of
+ * the headers are defined as {@code String} constants in the headers class.
+ * <p/>
+ * The class to provide can be any class but by convention, we would expect a class whose name is of type
+ * <i>xxxConstants</i> where <i>xxx</i> is the name of the corresponding component like for example
+ * <i>FtpConstants</i> for the component <i>camel-ftp</i>.
+ * <p/>
+ * The metadata of a given header are retrieved directly from the annotation {@code @Metadata} added to the
+ * {@code String} constant representing its name and defined in the headers class.
+ */
+ Class<?> headersClass() default void.class;
}
diff --git a/docs/components/modules/ROOT/partials/component-endpoint-headers.adoc b/docs/components/modules/ROOT/partials/component-endpoint-headers.adoc
new file mode 100644
index 0000000..94b11e8
--- /dev/null
+++ b/docs/components/modules/ROOT/partials/component-endpoint-headers.adoc
@@ -0,0 +1,24 @@
+//component headers: START
+
+:tablespec: width="100%",cols="2,5a,^1,2",options="header"
+:cellformats: 'util.boldLink(path[2], "endpoint_header", value.group) \
+|util.description(value) \
+|util.valueAsString(value.defaultValue) \
+|util.javaSimpleName(value.javaType)'
+include::jsonpath$example$json/{shortname}.json[query='$.component',formats='name,scheme,pascalcasescheme=util.pascalCase(scheme),syntax,apiSyntax', requires={requires}]
+include::jsonpathcount$example$json/{shortname}.json[queries='headercount=nodes$.headers.*']
+
+ifeval::[{headercount} != 0]
+== Message Headers
+
+The {doctitle} component supports {headercount} message header(s), which is/are listed below:
+
+[{tablespec}]
+|===
+| Name | Description | Default | Type
+|===
+
+jsonpathTable::example$json/{shortname}.json['nodes$.headers.*',{cellformats},{requires}]
+
+endif::[]
+// component headers: END
\ No newline at end of file
diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ComponentModel.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ComponentModel.java
index f4e5978..dfe31c0 100644
--- a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ComponentModel.java
+++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/ComponentModel.java
@@ -37,6 +37,7 @@ public class ComponentModel extends ArtifactModel<ComponentModel.ComponentOption
protected boolean lenientProperties;
protected String verifiers;
protected final List<EndpointOptionModel> endpointOptions = new ArrayList<>();
+ protected final List<EndpointHeaderModel> headers = new ArrayList<>();
// lets sort apis A..Z so they are always in the same order
protected final Collection<ApiModel> apiOptions = new TreeSet<>(Comparators.apiModelComparator());
@@ -160,6 +161,14 @@ public class ComponentModel extends ArtifactModel<ComponentModel.ComponentOption
endpointOptions.add(option);
}
+ public List<EndpointHeaderModel> getEndpointHeaders() {
+ return headers;
+ }
+
+ public void addEndpointHeader(EndpointHeaderModel header) {
+ headers.add(header);
+ }
+
public List<EndpointOptionModel> getEndpointParameterOptions() {
return endpointOptions.stream()
.filter(o -> "parameter".equals(o.getKind()))
@@ -176,6 +185,10 @@ public class ComponentModel extends ArtifactModel<ComponentModel.ComponentOption
return apiOptions;
}
+ public static class EndpointHeaderModel extends BaseOptionModel {
+
+ }
+
public static class ComponentOptionModel extends BaseOptionModel {
}
diff --git a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
index fde2830..d828168 100644
--- a/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
+++ b/tooling/camel-tooling-model/src/main/java/org/apache/camel/tooling/model/JsonMapper.java
@@ -27,6 +27,7 @@ import java.util.TreeMap;
import java.util.stream.Collectors;
import org.apache.camel.tooling.model.ComponentModel.ComponentOptionModel;
+import org.apache.camel.tooling.model.ComponentModel.EndpointHeaderModel;
import org.apache.camel.tooling.model.ComponentModel.EndpointOptionModel;
import org.apache.camel.tooling.model.DataFormatModel.DataFormatOptionModel;
import org.apache.camel.tooling.model.EipModel.EipOptionModel;
@@ -90,6 +91,15 @@ public final class JsonMapper {
model.addComponentOption(option);
}
}
+ JsonObject headers = (JsonObject) obj.get("headers");
+ if (headers != null) {
+ for (Map.Entry<String, Object> entry : headers.entrySet()) {
+ JsonObject mp = (JsonObject) entry.getValue();
+ EndpointHeaderModel header = new EndpointHeaderModel();
+ parseOption(mp, header, entry.getKey());
+ model.addEndpointHeader(header);
+ }
+ }
JsonObject mprp = (JsonObject) obj.get("properties");
if (mprp != null) {
for (Map.Entry<String, Object> entry : mprp.entrySet()) {
@@ -213,6 +223,10 @@ public final class JsonMapper {
JsonObject wrapper = new JsonObject();
wrapper.put("component", obj);
wrapper.put("componentProperties", asJsonObject(model.getComponentOptions()));
+ final List<EndpointHeaderModel> headers = model.getEndpointHeaders();
+ if (!headers.isEmpty()) {
+ wrapper.put("headers", asJsonObject(headers));
+ }
wrapper.put("properties", asJsonObject(model.getEndpointOptions()));
if (!model.getApiOptions().isEmpty()) {
wrapper.put("apis", apiModelAsJsonObject(model.getApiOptions(), false));
diff --git a/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java
new file mode 100644
index 0000000..fc03afa
--- /dev/null
+++ b/tooling/camel-tooling-model/src/test/java/org/apache/camel/tooling/model/JsonMapperTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.tooling.model;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.apache.camel.tooling.model.ComponentModel.EndpointHeaderModel;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The unit test class for {@link JsonMapper}.
+ */
+class JsonMapperTest {
+
+ @Test
+ void testShouldSerializeAndDeserializeComponentWithoutHeaders() {
+ ComponentModel model = new ComponentModel();
+ String json = JsonMapper.createParameterJsonSchema(model);
+ assertFalse(json.contains("\"headers\""));
+ ComponentModel model2 = JsonMapper.generateComponentModel(json);
+ assertTrue(model2.getEndpointHeaders().isEmpty());
+ }
+
+ @Test
+ void testShouldSerializeAndDeserializeComponentWithOneHeader() {
+ ComponentModel model = new ComponentModel();
+ EndpointHeaderModel header = new EndpointHeaderModel();
+ header.setName("Some Name");
+ header.setDescription("Some Description");
+ model.addEndpointHeader(header);
+ String json = JsonMapper.createParameterJsonSchema(model);
+ ComponentModel model2 = JsonMapper.generateComponentModel(json);
+ List<EndpointHeaderModel> headers = model2.getEndpointHeaders();
+ assertEquals(1, headers.size());
+ assertEquals(header.getName(), headers.get(0).getName());
+ assertEquals(header.getDescription(), headers.get(0).getDescription());
+ }
+
+ @Test
+ void testShouldSerializeAndDeserializeComponentWithSeveralHeaders() {
+ ComponentModel model = new ComponentModel();
+ EndpointHeaderModel header1 = new EndpointHeaderModel();
+ header1.setName("Some Name");
+ header1.setDescription("Some Description");
+ model.addEndpointHeader(header1);
+ EndpointHeaderModel header2 = new EndpointHeaderModel();
+ header2.setName("Some Name 2");
+ header2.setDescription("Some Description 2");
+ model.addEndpointHeader(header2);
+ String json = JsonMapper.createParameterJsonSchema(model);
+ ComponentModel model2 = JsonMapper.generateComponentModel(json);
+ List<EndpointHeaderModel> headers = model2.getEndpointHeaders();
+ assertEquals(2, headers.size());
+ assertEquals(header1.getName(), headers.get(0).getName());
+ assertEquals(header1.getDescription(), headers.get(0).getDescription());
+ assertEquals(header2.getName(), headers.get(1).getName());
+ assertEquals(header2.getDescription(), headers.get(1).getDescription());
+ }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/pom.xml b/tooling/maven/camel-package-maven-plugin/pom.xml
index 29fcf2b..39c6839 100644
--- a/tooling/maven/camel-package-maven-plugin/pom.xml
+++ b/tooling/maven/camel-package-maven-plugin/pom.xml
@@ -188,6 +188,46 @@
</mojoDependencies>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-invoker-plugin</artifactId>
+ <configuration>
+ <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
+ <pomIncludes>
+ <pomInclude>*/pom.xml</pomInclude>
+ </pomIncludes>
+ <postBuildHookScript>verify</postBuildHookScript>
+ <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
+ <settingsFile>src/it/settings.xml</settingsFile>
+ <goals>
+ <goal>clean</goal>
+ <goal>verify</goal>
+ </goals>
+ </configuration>
+ <executions>
+ <execution>
+ <id>integration-test</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>install</goal>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ <version>${groovy-version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-tooling-model</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
</plugins>
</build>
@@ -205,5 +245,17 @@
</dependency>
</dependencies>
</profile>
+ <profile>
+ <id>fastinstall</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ <property>
+ <name>skipTests</name>
+ </property>
+ </activation>
+ <properties>
+ <invoker.skip>true</invoker.skip>
+ </properties>
+ </profile>
</profiles>
</project>
diff --git a/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/pom.xml b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/pom.xml
new file mode 100644
index 0000000..2c2e648
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.camel.integration.test</groupId>
+ <artifactId>header-support</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+ <description>An IT ensuring that headers are supported.</description>
+ <properties>
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <!-- Add Camel BOM -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-bom</artifactId>
+ <version>@project.version@</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-core</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>@project.groupId@</groupId>
+ <artifactId>@project.artifactId@</artifactId>
+ <version>@project.version@</version>
+ <executions>
+ <execution>
+ <id>generate</id>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ <phase>process-classes</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooComponent.java b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooComponent.java
new file mode 100644
index 0000000..6a6fed3
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooComponent.java
@@ -0,0 +1,26 @@
+/*
+ * 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.foo;
+
+import org.apache.camel.spi.annotations.Component;
+
+/**
+ * Foo Component
+ */
+@Component("foo")
+public class FooComponent {
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooConstants.java b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooConstants.java
new file mode 100644
index 0000000..3c7886a
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooConstants.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.component.foo;
+
+import org.apache.camel.spi.Metadata;
+
+public final class FooConstants {
+
+ @Metadata(description = "My description of the SomeHeader")
+ public static final String SOME_HEADER = "SomeHeaderName";
+
+ private FooConstants() {
+ }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooEndpoint.java b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooEndpoint.java
new file mode 100644
index 0000000..11c178a
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/FooEndpoint.java
@@ -0,0 +1,70 @@
+/*
+ * 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.foo;
+
+import org.apache.camel.Category;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriEndpoint;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriPath;
+
+@UriEndpoint(firstVersion = "1.1.0", scheme = "foo", extendsScheme = "foo", title = "FOO",
+ syntax = "foo:host:port", alternativeSyntax = "foo:host@port",
+ category = { Category.FILE }, headersClass = FooConstants.class)
+public class FooEndpoint {
+
+ @UriPath(description = "Hostname of the Foo server")
+ @Metadata(required = true)
+ private String host;
+ @UriPath(description = "Port of the Foo server")
+ private int port;
+ @UriParam(label = "common", defaultValue = "5")
+ private int intervalSeconds = 5;
+
+ public int getIntervalSeconds() {
+ return intervalSeconds;
+ }
+
+ /**
+ * My interval in seconds.
+ */
+ public void setIntervalSeconds(int intervalSeconds) {
+ this.intervalSeconds = intervalSeconds;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Hostname of the Foo server
+ */
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Port of the Foo server
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/verify.groovy b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/verify.groovy
new file mode 100644
index 0000000..a1ce29b
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/verify.groovy
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+import org.apache.camel.tooling.model.ComponentModel
+import org.apache.camel.tooling.model.JsonMapper
+
+File json = new File(basedir, "src/generated/resources/org/apache/camel/component/foo/foo.json")
+assert json.exists()
+
+ComponentModel component = JsonMapper.generateComponentModel(json.text)
+assert component.getEndpointHeaders().size() == 1
+assert "My description of the SomeHeader".equals(component.getEndpointHeaders().get(0).getDescription())
diff --git a/tooling/maven/camel-package-maven-plugin/src/it/settings.xml b/tooling/maven/camel-package-maven-plugin/src/it/settings.xml
new file mode 100644
index 0000000..c72c23c
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/it/settings.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<settings>
+ <profiles>
+ <profile>
+ <id>it-repo</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <repositories>
+ <repository>
+ <id>local.central</id>
+ <url>@localRepositoryUrl@</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+ <pluginRepositories>
+ <pluginRepository>
+ <id>local.central</id>
+ <url>@localRepositoryUrl@</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </pluginRepository>
+ </pluginRepositories>
+ </profile>
+ </profiles>
+</settings>
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java
index 6ff45ed..1bc68d8 100644
--- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojo.java
@@ -64,6 +64,7 @@ import org.apache.camel.tooling.model.ApiModel;
import org.apache.camel.tooling.model.BaseOptionModel;
import org.apache.camel.tooling.model.ComponentModel;
import org.apache.camel.tooling.model.ComponentModel.ComponentOptionModel;
+import org.apache.camel.tooling.model.ComponentModel.EndpointHeaderModel;
import org.apache.camel.tooling.model.ComponentModel.EndpointOptionModel;
import org.apache.camel.tooling.model.JsonMapper;
import org.apache.camel.tooling.model.SupportLevel;
@@ -92,7 +93,8 @@ import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexReader;
import org.jboss.jandex.IndexView;
-import static org.apache.camel.tooling.model.ComponentModel.*;
+import static java.lang.reflect.Modifier.isStatic;
+import static org.apache.camel.tooling.model.ComponentModel.ApiOptionModel;
@Mojo(name = "generate-endpoint-schema", threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
defaultPhase = LifecyclePhase.PROCESS_CLASSES)
@@ -258,6 +260,9 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo {
}
}
+ // component headers
+ addEndpointHeaders(componentModel, uriEndpoint);
+
// endpoint options
findClassProperties(componentModel, classElement, new HashSet<>(), "", null, null, false);
@@ -291,6 +296,85 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo {
return componentModel;
}
+ /**
+ * Retrieve the metadata added to all the {@code String} constants defined in class corresponding to the element
+ * {@code headersClass} of the annotation {@code UriEndpoint}, convert the metadata found into instances of
+ * {@link EndpointHeaderModel} and finally add the instances of {@link EndpointHeaderModel} to the given component
+ * model.
+ *
+ * @param componentModel the component model to which the headers should be added.
+ * @param uriEndpoint the annotation from which the headers class is retrieved.
+ */
+ void addEndpointHeaders(ComponentModel componentModel, UriEndpoint uriEndpoint) {
+ final Class<?> headersClass = uriEndpoint.headersClass();
+ if (headersClass == void.class) {
+ getLog().debug(String.format("The endpoint %s has not defined any headers class", uriEndpoint.scheme()));
+ return;
+ }
+ // A header class has been defined
+ boolean foundHeader = false;
+ for (Field field : headersClass.getDeclaredFields()) {
+ if (isStatic(field.getModifiers()) && field.getType() == String.class
+ && field.isAnnotationPresent(Metadata.class)) {
+ getLog().debug(
+ String.format("Trying to add the constant %s in the class %s as header.", field.getName(),
+ headersClass.getName()));
+ addEndpointHeader(componentModel, field);
+ foundHeader = true;
+ continue;
+ }
+ getLog().debug(
+ String.format("The field %s of the class %s is not considered as a name of a header, thus it is skipped",
+ field.getName(), headersClass.getName()));
+ }
+ if (!foundHeader) {
+ getLog().debug(String.format("No headers have been detected in the headers class %s", headersClass.getName()));
+ }
+ }
+
+ /**
+ * Retrieve the metadata added to the given field, convert the metadata found into an instance of
+ * {@link EndpointHeaderModel} and finally add the instance of {@link EndpointHeaderModel} to the given component
+ * model.
+ *
+ * @param componentModel the component to which the header should be added.
+ * @param field the field corresponding to the constant from which the metadata should be extracted.
+ */
+ private void addEndpointHeader(ComponentModel componentModel, Field field) {
+ final Metadata metadata = field.getAnnotation(Metadata.class);
+ if (metadata == null) {
+ getLog().debug(String.format("The field %s in class %s has no Metadata", field.getName(),
+ field.getDeclaringClass().getName()));
+ return;
+ }
+ final EndpointHeaderModel header = new EndpointHeaderModel();
+ header.setDescription(metadata.description().trim());
+ header.setKind("header");
+ header.setDisplayName(metadata.displayName());
+ header.setJavaType(metadata.javaType());
+ header.setRequired(metadata.required());
+ header.setDefaultValue(metadata.defaultValue());
+ header.setDeprecated(field.isAnnotationPresent(Deprecated.class));
+ header.setDeprecationNote(metadata.deprecationNote());
+ header.setSecret(metadata.secret());
+ header.setGroup(EndpointHelper.labelAsGroupName(metadata.label(), componentModel.isConsumerOnly(),
+ componentModel.isProducerOnly()));
+ header.setLabel(metadata.label());
+ try {
+ header.setEnums(getEnums(metadata, header.getJavaType().isEmpty() ? null : loadClass(header.getJavaType())));
+ } catch (NoClassDefFoundError e) {
+ getLog().debug(String.format("The java type %s could not be found", header.getJavaType()), e);
+ }
+ try {
+ field.trySetAccessible();
+ header.setName((String) field.get(null));
+ componentModel.addEndpointHeader(header);
+ } catch (IllegalAccessException e) {
+ getLog().debug(String.format("The field %s in class %s cannot be accessed", field.getName(),
+ field.getDeclaringClass().getName()));
+ }
+ }
+
private String getExcludedEnd(Metadata classElement) {
String excludedEndpointProperties = "";
if (classElement != null) {
@@ -730,19 +814,7 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo {
}
// gather enums
- List<String> enums = null;
- if (metadata != null && !Strings.isNullOrEmpty(metadata.enums())) {
- String[] values = metadata.enums().split(",");
- enums = Stream.of(values).map(String::trim).collect(Collectors.toList());
- } else if (fieldType.isEnum()) {
- enums = new ArrayList<>();
- for (Object val : fieldType.getEnumConstants()) {
- String str = val.toString();
- if (!enums.contains(str)) {
- enums.add(str);
- }
- }
- }
+ List<String> enums = getEnums(metadata, fieldType);
// the field type may be overloaded by another type
boolean isDuration = false;
@@ -839,6 +911,23 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo {
}
}
+ private List<String> getEnums(Metadata metadata, Class<?> fieldType) {
+ List<String> enums = null;
+ if (metadata != null && !Strings.isNullOrEmpty(metadata.enums())) {
+ String[] values = metadata.enums().split(",");
+ enums = Stream.of(values).map(String::trim).collect(Collectors.toList());
+ } else if (fieldType != null && fieldType.isEnum()) {
+ enums = new ArrayList<>();
+ for (Object val : fieldType.getEnumConstants()) {
+ String str = val.toString();
+ if (!enums.contains(str)) {
+ enums.add(str);
+ }
+ }
+ }
+ return enums;
+ }
+
private Field getFieldElement(Class<?> classElement, String fieldName) {
Field fieldElement;
try {
diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
new file mode 100644
index 0000000..8e65a7d
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.maven.packaging;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.camel.maven.packaging.endpoint.SomeEndpoint;
+import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithBadHeaders;
+import org.apache.camel.maven.packaging.endpoint.SomeEndpointWithoutHeaders;
+import org.apache.camel.spi.UriEndpoint;
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.ComponentModel.EndpointHeaderModel;
+import org.apache.maven.project.MavenProject;
+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.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The unit test of {@link EndpointSchemaGeneratorMojo}.
+ */
+class EndpointSchemaGeneratorMojoTest {
+
+ private final EndpointSchemaGeneratorMojo mojo = new EndpointSchemaGeneratorMojo();
+ private final ComponentModel model = new ComponentModel();
+
+ @BeforeEach
+ void init() throws Exception {
+ mojo.sourceRoots = List.of(
+ Paths.get(EndpointSchemaGeneratorMojoTest.class.getResource("/").toURI())
+ .resolve("../../src/test/java/"));
+ mojo.project = new MavenProject();
+ }
+
+ @Test
+ void testCanRetrieveMetadataOfHeaders() {
+ mojo.addEndpointHeaders(model, SomeEndpoint.class.getAnnotation(UriEndpoint.class));
+ List<EndpointHeaderModel> endpointHeaders = model.getEndpointHeaders();
+ assertEquals(2, endpointHeaders.size());
+ // Full
+ EndpointHeaderModel headerFull = endpointHeaders.get(0);
+ assertEquals("header", headerFull.getKind());
+ assertEquals("name-full", headerFull.getName());
+ assertEquals("key full desc", headerFull.getDescription());
+ assertEquals("my display name", headerFull.getDisplayName());
+ assertEquals("org.apache.camel.maven.packaging.endpoint.SomeEndpoint$MyEnum", headerFull.getJavaType());
+ assertTrue(headerFull.isRequired());
+ assertEquals("VAL1", headerFull.getDefaultValue());
+ assertTrue(headerFull.isDeprecated());
+ assertEquals("my deprecated note", headerFull.getDeprecationNote());
+ assertTrue(headerFull.isSecret());
+ assertEquals("my label", headerFull.getLabel());
+ assertEquals(3, headerFull.getEnums().size());
+ assertEquals("my label", headerFull.getGroup());
+ // Empty
+ EndpointHeaderModel headerEmpty = endpointHeaders.get(1);
+ assertEquals("header", headerEmpty.getKind());
+ assertEquals("name-empty", headerEmpty.getName());
+ assertTrue(headerEmpty.getDescription().isEmpty());
+ assertTrue(headerEmpty.getDisplayName().isEmpty());
+ assertTrue(headerEmpty.getJavaType().isEmpty());
+ assertFalse(headerEmpty.isRequired());
+ assertInstanceOf(String.class, headerEmpty.getDefaultValue());
+ assertTrue(((String) headerEmpty.getDefaultValue()).isEmpty());
+ assertFalse(headerEmpty.isDeprecated());
+ assertTrue(headerEmpty.getDeprecationNote().isEmpty());
+ assertFalse(headerEmpty.isSecret());
+ assertTrue(headerEmpty.getLabel().isEmpty());
+ assertNull(headerEmpty.getEnums());
+ assertEquals("common", headerEmpty.getGroup());
+ }
+
+ @Test
+ void testHeadersNotProperlyDefinedAreIgnored() {
+ mojo.addEndpointHeaders(model, SomeEndpointWithBadHeaders.class.getAnnotation(UriEndpoint.class));
+ assertEquals(0, model.getEndpointHeaders().size());
+ }
+
+ @Test
+ void testEndpointWithoutHeadersAreIgnored() {
+ mojo.addEndpointHeaders(model, SomeEndpointWithoutHeaders.class.getAnnotation(UriEndpoint.class));
+ assertEquals(0, model.getEndpointHeaders().size());
+ }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeConstants.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeConstants.java
new file mode 100644
index 0000000..ae4bbb3
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeConstants.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.maven.packaging.endpoint;
+
+import org.apache.camel.spi.Metadata;
+
+public final class SomeConstants {
+ @Deprecated
+ @Metadata(description = "key full desc", label = "my label", displayName = "my display name",
+ javaType = "org.apache.camel.maven.packaging.endpoint.SomeEndpoint$MyEnum", required = true,
+ defaultValue = "VAL1", deprecationNote = "my deprecated note", secret = true)
+ public static final String KEY_FULL = "name-full";
+ @Metadata
+ static final String KEY_EMPTY = "name-empty";
+
+ private SomeConstants() {
+ }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpoint.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpoint.java
new file mode 100644
index 0000000..d421d44
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpoint.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.maven.packaging.endpoint;
+
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriEndpoint;
+import org.apache.camel.spi.UriPath;
+
+@UriEndpoint(scheme = "some", syntax = "some", title = "some", headersClass = SomeConstants.class)
+public class SomeEndpoint {
+
+ @UriPath(description = "Hostname of the Foo server")
+ @Metadata(required = true)
+ private String host;
+
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Hostname of the Foo server
+ */
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public enum MyEnum {
+ VAL1,
+ VAL2,
+ VAL3
+ }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadHeaders.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadHeaders.java
new file mode 100644
index 0000000..88b115d
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadHeaders.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.maven.packaging.endpoint;
+
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriEndpoint;
+
+@UriEndpoint(scheme = "some", syntax = "some", title = "some", headersClass = SomeEndpointWithBadHeaders.class)
+public final class SomeEndpointWithBadHeaders {
+ public static final String NO_METADATA = "no-metadata";
+ @Metadata(description = "some description")
+ public static final int NOT_A_STRING = 1;
+ @Metadata(description = "some description")
+ public final String notStatic = "not-static";
+
+ private SomeEndpointWithBadHeaders() {
+
+ }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithoutHeaders.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithoutHeaders.java
new file mode 100644
index 0000000..13f04ca
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithoutHeaders.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.maven.packaging.endpoint;
+
+import org.apache.camel.spi.UriEndpoint;
+
+@UriEndpoint(scheme = "some", syntax = "some", title = "some", headersClass = SomeEndpointWithoutHeaders.class)
+public class SomeEndpointWithoutHeaders {
+}
diff --git a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/UriEndpoint.java b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/UriEndpoint.java
index a3e5a64..d92ea81 100644
--- a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/UriEndpoint.java
+++ b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/UriEndpoint.java
@@ -175,4 +175,16 @@ public @interface UriEndpoint {
*/
String apiSyntax() default "";
+ /**
+ * The class that contains all the name of headers that are supported by the consumer and/or producer. The name of
+ * the headers are defined as {@code String} constants in the headers class.
+ * <p/>
+ * The class to provide can be any class but by convention, we would expect a class whose name is of type
+ * <i>xxxConstants</i> where <i>xxx</i> is the name of the corresponding component like for example
+ * <i>FtpConstants</i> for the component <i>camel-ftp</i>.
+ * <p/>
+ * The metadata of a given header are retrieved directly from the annotation {@code @Metadata} added to the
+ * {@code String} constant representing its name and defined in the headers class.
+ */
+ Class<?> headersClass() default void.class;
}