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/11 15:22:17 UTC

[camel] 01/01: CAMEL-16628: Allow to generate doc about message headers

This is an automated email from the ASF dual-hosted git repository.

nfilotto pushed a commit to branch CAMEL-16628/headers-annotation
in repository https://gitbox.apache.org/repos/asf/camel.git

commit edcecc37267e3f21dce61ce99f86121494700877
Author: Nicolas Filotto <nf...@talend.com>
AuthorDate: Fri Mar 11 11:35:12 2022 +0100

    CAMEL-16628: Allow to generate doc about message headers
---
 .../java/org/apache/camel/spi/Metadata.java        |   9 +
 .../ROOT/partials/component-endpoint-headers.adoc  |  24 +++
 .../apache/camel/tooling/model/ComponentModel.java |  13 ++
 .../org/apache/camel/tooling/model/JsonMapper.java |  20 ++
 .../apache/camel/tooling/model/JsonMapperTest.java |  77 ++++++++
 tooling/maven/camel-package-maven-plugin/pom.xml   |  52 ++++++
 .../src/it/HeaderSupport/pom.xml                   |  70 +++++++
 .../camel/component/foo/ConstantsSamePackage.java  |  28 +++
 .../apache/camel/component/foo/FooComponent.java   |  26 +++
 .../apache/camel/component/foo/FooEndpoint.java    |  71 ++++++++
 .../src/it/HeaderSupport/verify.groovy             |  25 +++
 .../camel-package-maven-plugin/src/it/settings.xml |  53 ++++++
 .../packaging/EndpointSchemaGeneratorMojo.java     | 201 +++++++++++++++++++--
 .../packaging/EndpointSchemaGeneratorMojoTest.java | 102 +++++++++++
 .../packaging/endpoint/SamePackageConstants.java   |  46 +++++
 .../maven/packaging/endpoint/SomeEndpoint.java     |  96 ++++++++++
 .../endpoint/SomeEndpointWithBadHeaders.java       |  29 +++
 .../endpoint/SomeEndpointWithoutHeaders.java       |  23 +++
 .../endpoint/other/OtherPackageConstants.java      |  49 +++++
 .../main/java/org/apache/camel/spi/Metadata.java   |   9 +
 20 files changed, 1009 insertions(+), 14 deletions(-)

diff --git a/core/camel-api/src/generated/java/org/apache/camel/spi/Metadata.java b/core/camel-api/src/generated/java/org/apache/camel/spi/Metadata.java
index f37ea43..a099566 100644
--- a/core/camel-api/src/generated/java/org/apache/camel/spi/Metadata.java
+++ b/core/camel-api/src/generated/java/org/apache/camel/spi/Metadata.java
@@ -132,4 +132,13 @@ public @interface Metadata {
      * specify which options each implementation only supports.
      */
     String includeProperties() default "";
+
+    /**
+     * All the headers that are supported by the consumer and/or producer.
+     * <p/>
+     * The expected values are actually the constant expressions referring the name of the header like for example
+     * {@code KafkaConstants.PARTITION_KEY}. The metadata of each header are retrieved directly from the annotation
+     * {@code @Metadata} added to the corresponding constant.
+     */
+    String[] headers() default {};
 }
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..86fe787 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));
@@ -527,6 +541,12 @@ public final class JsonMapper {
         return prop;
     }
 
+    public static JsonObject asJsonObject(EndpointHeaderModel header) {
+        JsonObject prop = new JsonObject();
+        prop.put("description", header.getDescription());
+        return prop;
+    }
+
     public static MainModel generateMainModel(String json) {
         JsonObject obj = deserialize(json);
         return generateMainModel(obj);
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/ConstantsSamePackage.java b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/ConstantsSamePackage.java
new file mode 100644
index 0000000..34977df
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/src/main/java/org/apache/camel/component/foo/ConstantsSamePackage.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 ConstantsSamePackage {
+
+    @Metadata(description = "My Constant in same package")
+    public static final String SAME_PACKAGE_KEY = "SomeKeyName";
+
+    private ConstantsSamePackage() {
+    }
+}
\ No newline at end of file
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..0e2d4e7
--- /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 {
+}
\ No newline at end of file
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..b994ecd
--- /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,71 @@
+/*
+ * 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 })
+@Metadata(headers = ConstantsSamePackage.SAME_PACKAGE_KEY)
+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;
+    }
+}
\ No newline at end of file
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..ab2dc14
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/it/HeaderSupport/verify.groovy
@@ -0,0 +1,25 @@
+/*
+ * 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
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..abfc771 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;
@@ -83,7 +84,10 @@ import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.ASTNode;
 import org.jboss.forge.roaster._shade.org.eclipse.jdt.core.dom.Javadoc;
 import org.jboss.forge.roaster.model.JavaDoc;
 import org.jboss.forge.roaster.model.JavaDocCapable;
+import org.jboss.forge.roaster.model.StaticCapable;
+import org.jboss.forge.roaster.model.source.AnnotationSource;
 import org.jboss.forge.roaster.model.source.FieldSource;
+import org.jboss.forge.roaster.model.source.Import;
 import org.jboss.forge.roaster.model.source.JavaClassSource;
 import org.jboss.forge.roaster.model.source.MethodSource;
 import org.jboss.jandex.AnnotationInstance;
@@ -92,7 +96,7 @@ 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 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 +262,9 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo {
             }
         }
 
+        // component headers
+        addEndpointHeaders(componentModel, classElement);
+
         // endpoint options
         findClassProperties(componentModel, classElement, new HashSet<>(), "", null, null, false);
 
@@ -291,6 +298,167 @@ public class EndpointSchemaGeneratorMojo extends AbstractGeneratorMojo {
         return componentModel;
     }
 
+    /**
+     * Retrieve the metadata added to the constants specified in the element {@code headers} of the annotation
+     * {@code Metadata}, convert the metadata retrieved 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 classElement   the class from which the annotation {@code Metadata} will be retrieved.
+     */
+    void addEndpointHeaders(ComponentModel componentModel, Class<?> classElement) {
+        final Metadata componentMetadata = classElement.getAnnotation(Metadata.class);
+        if (componentMetadata == null || componentMetadata.headers().length == 0) {
+            return;
+        }
+        // There are headers to load
+        try {
+            final JavaClassSource source = javaClassSource(classElement.getName());
+            final AnnotationSource<?> annotationSource = source.getAnnotation(Metadata.class);
+            if (annotationSource == null) {
+                getLog().warn(String.format("The annotation @Metadata could not be found by the parser in the class %s",
+                        classElement.getName()));
+                return;
+            }
+            final String[] headers = annotationSource.getStringArrayValue("headers");
+            if (headers == null) {
+                getLog().warn(String.format("The element headers of @Metadata could not be found by the parser in the class %s",
+                        classElement.getName()));
+                return;
+            }
+            for (String headerRef : headers) {
+                getLog().debug(String.format("Header ref found %s", headerRef));
+                addEndpointHeader(componentModel, source, headerRef);
+            }
+        } catch (Exception e) {
+            getLog().warn(String.format("The headers of %s could not be loaded", classElement.getName()), e);
+        }
+    }
+
+    /**
+     * Identify the constant corresponding to the given header reference and retrieve the metadata added to it, convert
+     * the metadata retrieved 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 source         the source of the file that contains the definition of the header references.
+     * @param headerRef      the header reference to process.
+     */
+    private void addEndpointHeader(ComponentModel componentModel, JavaClassSource source, String headerRef) {
+        final int lastIndex = headerRef.lastIndexOf('.');
+        final String constantName;
+        final String qualifiedName;
+        if (lastIndex == -1) {
+            // No prefix
+            final Optional<Import> optionalImport = source.getImports().stream()
+                    .filter(StaticCapable::isStatic)
+                    .filter(imp -> imp.getSimpleName().equals(headerRef))
+                    .findFirst();
+            if (optionalImport.isPresent()) {
+                constantName = headerRef;
+                final String importQualifiedName = optionalImport.get().getQualifiedName();
+                qualifiedName = importQualifiedName.substring(0, importQualifiedName.length() - headerRef.length() - 1);
+            } else {
+                getLog().debug(String.format("The header %s defined in the class %s could not be found", headerRef,
+                        source.getQualifiedName()));
+                return;
+            }
+        } else if (headerRef.startsWith(source.getPackage())) {
+            // Fully qualified
+            constantName = headerRef.substring(lastIndex + 1);
+            qualifiedName = headerRef.substring(0, lastIndex);
+        } else {
+            // With prefix
+            final String prefix = headerRef.substring(0, lastIndex);
+            final int firstIndex = prefix.indexOf('.');
+            final String root = firstIndex == -1 ? prefix : prefix.substring(0, firstIndex);
+            final Optional<Import> optionalImport = source.getImports().stream()
+                    .filter(imp -> !imp.isStatic())
+                    .filter(imp -> imp.getQualifiedName().endsWith(root))
+                    .findFirst();
+            constantName = headerRef.substring(lastIndex + 1);
+            if (optionalImport.isPresent()) {
+                // In another package
+                if (firstIndex == -1) {
+                    // No inner class
+                    qualifiedName = optionalImport.get().getQualifiedName();
+                } else {
+                    // In inner class
+                    qualifiedName = String.format("%s$%s", optionalImport.get().getQualifiedName(),
+                            prefix.substring(firstIndex + 1).replace('.', '$'));
+                }
+            } else {
+                // In the same package
+                qualifiedName = String.format("%s.%s", source.getPackage(), prefix);
+            }
+        }
+        addEndpointHeader(componentModel, constantName, qualifiedName);
+    }
+
+    /**
+     * Retrieve the metadata added to the given constant of the given class, convert the metadata retrieved 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 constantName   the name of the constant from which the metadata defining the header should be extracted.
+     * @param qualifiedName  the full qualified name of the class containing the constant.
+     */
+    private void addEndpointHeader(ComponentModel componentModel, String constantName, String qualifiedName) {
+        getLog().debug(
+                String.format("Trying to add the constant %s in the class %s as header.", constantName, qualifiedName));
+        try {
+            addEndpointHeader(componentModel, loadClass(qualifiedName).getDeclaredField(constantName));
+        } catch (NoClassDefFoundError e) {
+            getLog().debug(String.format("The class %s could not be found", qualifiedName), e);
+        } catch (NoSuchFieldException e) {
+            getLog().debug(String.format("The field %s in class %s could not be found", constantName, qualifiedName), e);
+        }
+    }
+
+    /**
+     * Retrieve the metadata added to the given field, convert the metadata retrieved 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 +898,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 +995,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..98890b5
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/EndpointSchemaGeneratorMojoTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.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);
+        List<EndpointHeaderModel> endpointHeaders = model.getEndpointHeaders();
+        assertEquals(18, endpointHeaders.size());
+        for (int i = 1; i < endpointHeaders.size(); i++) {
+            EndpointHeaderModel header = endpointHeaders.get(i);
+            assertEquals("header", header.getKind());
+            assertEquals(String.format("name-%d", i + 1), header.getName());
+            assertEquals(String.format("key%d desc", i + 1), header.getDescription());
+            assertTrue(header.getDisplayName().isEmpty());
+            assertTrue(header.getJavaType().isEmpty());
+            assertFalse(header.isRequired());
+            assertInstanceOf(String.class, header.getDefaultValue());
+            assertTrue(((String) header.getDefaultValue()).isEmpty());
+            assertFalse(header.isDeprecated());
+            assertTrue(header.getDeprecationNote().isEmpty());
+            assertFalse(header.isSecret());
+            assertTrue(header.getLabel().isEmpty());
+            assertNull(header.getEnums());
+            assertEquals("common", header.getGroup());
+        }
+        EndpointHeaderModel header = endpointHeaders.get(0);
+        assertEquals("header", header.getKind());
+        assertEquals("name-1", header.getName());
+        assertEquals("key1 desc", header.getDescription());
+        assertEquals("my display name", header.getDisplayName());
+        assertEquals("org.apache.camel.maven.packaging.endpoint.SomeEndpoint$MyEnum", header.getJavaType());
+        assertTrue(header.isRequired());
+        assertEquals("VAL1", header.getDefaultValue());
+        assertTrue(header.isDeprecated());
+        assertEquals("my deprecated note", header.getDeprecationNote());
+        assertTrue(header.isSecret());
+        assertEquals("my label", header.getLabel());
+        assertEquals(3, header.getEnums().size());
+        assertEquals("my label", header.getGroup());
+    }
+
+    @Test
+    void testHeadersNotProperlyDefinedAreIgnored() {
+        mojo.addEndpointHeaders(model, SomeEndpointWithBadHeaders.class);
+        assertEquals(0, model.getEndpointHeaders().size());
+    }
+
+    @Test
+    void testEndpointWithoutHeadersAreIgnored() {
+        mojo.addEndpointHeaders(model, SomeEndpointWithoutHeaders.class);
+        assertEquals(0, model.getEndpointHeaders().size());
+    }
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SamePackageConstants.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SamePackageConstants.java
new file mode 100644
index 0000000..1d1debb
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SamePackageConstants.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;
+
+public final class SamePackageConstants {
+    @Metadata(description = "key2 desc")
+    static final String KEY_2 = "name-2";
+    @Metadata(description = "key17 desc")
+    static final String KEY_17 = "name-17";
+
+    private SamePackageConstants() {
+    }
+
+    public final class Inner {
+        @Metadata(description = "key7 desc")
+        public static final String KEY_7 = "name-7";
+        @Metadata(description = "key11 desc")
+        public static final String KEY_11 = "name-11";
+
+        private Inner() {
+        }
+    }
+
+    public static class InnerStatic {
+        @Metadata(description = "key8 desc")
+        public static final String KEY_8 = "name-8";
+        @Metadata(description = "key12 desc")
+        public static final String KEY_12 = "name-12";
+    }
+}
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..ba61373
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpoint.java
@@ -0,0 +1,96 @@
+/*
+ * 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.maven.packaging.endpoint.other.OtherPackageConstants;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriPath;
+
+import static org.apache.camel.maven.packaging.endpoint.SamePackageConstants.Inner.KEY_7;
+import static org.apache.camel.maven.packaging.endpoint.SamePackageConstants.InnerStatic.KEY_8;
+import static org.apache.camel.maven.packaging.endpoint.SomeEndpoint.Inner.KEY_13;
+import static org.apache.camel.maven.packaging.endpoint.SomeEndpoint.InnerStatic.KEY_14;
+import static org.apache.camel.maven.packaging.endpoint.other.OtherPackageConstants.KEY_4;
+import static org.apache.camel.maven.packaging.endpoint.other.OtherPackageConstants.Inner.KEY_9;
+import static org.apache.camel.maven.packaging.endpoint.other.OtherPackageConstants.InnerStatic.KEY_10;
+
+@Metadata(
+          headers = {
+                  SomeEndpoint.KEY_1,
+                  SamePackageConstants.KEY_2,
+                  OtherPackageConstants.KEY_3,
+                  KEY_4,
+                  OtherPackageConstants.Inner.KEY_5,
+                  OtherPackageConstants.InnerStatic.KEY_6,
+                  KEY_7,
+                  KEY_8,
+                  KEY_9,
+                  KEY_10,
+                  SamePackageConstants.Inner.KEY_11,
+                  SamePackageConstants.InnerStatic.KEY_12,
+                  KEY_13,
+                  KEY_14,
+                  SomeEndpoint.Inner.KEY_15,
+                  SomeEndpoint.InnerStatic.KEY_16,
+                  org.apache.camel.maven.packaging.endpoint.SamePackageConstants.KEY_17,
+                  org.apache.camel.maven.packaging.endpoint.other.OtherPackageConstants.KEY_18
+          })
+public class SomeEndpoint {
+    @Deprecated
+    @Metadata(description = "key1 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_1 = "name-1";
+
+    @UriPath(description = "Hostname of the Foo server")
+    @Metadata(required = true)
+    private String host;
+
+    public final class Inner {
+        @Metadata(description = "key13 desc")
+        public static final String KEY_13 = "name-13";
+        @Metadata(description = "key15 desc")
+        public static final String KEY_15 = "name-15";
+
+        private Inner() {
+        }
+    }
+
+    public static class InnerStatic {
+        @Metadata(description = "key14 desc")
+        public static final String KEY_14 = "name-14";
+        @Metadata(description = "key16 desc")
+        public static final String KEY_16 = "name-16";
+    }
+
+    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..94e2dec
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/SomeEndpointWithBadHeaders.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+@Metadata(
+          headers = { "non-existing", SomeEndpointWithBadHeaders.NO_METADATA })
+public final class SomeEndpointWithBadHeaders {
+    public static final String NO_METADATA = "no-metadata";
+
+    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..0d7fa93
--- /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.Metadata;
+
+@Metadata
+public class SomeEndpointWithoutHeaders {
+}
diff --git a/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/other/OtherPackageConstants.java b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/other/OtherPackageConstants.java
new file mode 100644
index 0000000..afdd9ef
--- /dev/null
+++ b/tooling/maven/camel-package-maven-plugin/src/test/java/org/apache/camel/maven/packaging/endpoint/other/OtherPackageConstants.java
@@ -0,0 +1,49 @@
+/*
+ * 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.other;
+
+import org.apache.camel.spi.Metadata;
+
+public final class OtherPackageConstants {
+    @Metadata(description = "key3 desc")
+    public static final String KEY_3 = "name-3";
+    @Metadata(description = "key4 desc")
+    public static final String KEY_4 = "name-4";
+    @Metadata(description = "key18 desc")
+    public static final String KEY_18 = "name-18";
+
+    private OtherPackageConstants() {
+
+    }
+
+    public final class Inner {
+        @Metadata(description = "key5 desc")
+        public static final String KEY_5 = "name-5";
+        @Metadata(description = "key9 desc")
+        public static final String KEY_9 = "name-9";
+
+        private Inner() {
+        }
+    }
+
+    public static class InnerStatic {
+        @Metadata(description = "key6 desc")
+        public static final String KEY_6 = "name-6";
+        @Metadata(description = "key10 desc")
+        public static final String KEY_10 = "name-10";
+    }
+}
diff --git a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/Metadata.java b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/Metadata.java
index f37ea43..a099566 100644
--- a/tooling/spi-annotations/src/main/java/org/apache/camel/spi/Metadata.java
+++ b/tooling/spi-annotations/src/main/java/org/apache/camel/spi/Metadata.java
@@ -132,4 +132,13 @@ public @interface Metadata {
      * specify which options each implementation only supports.
      */
     String includeProperties() default "";
+
+    /**
+     * All the headers that are supported by the consumer and/or producer.
+     * <p/>
+     * The expected values are actually the constant expressions referring the name of the header like for example
+     * {@code KafkaConstants.PARTITION_KEY}. The metadata of each header are retrieved directly from the annotation
+     * {@code @Metadata} added to the corresponding constant.
+     */
+    String[] headers() default {};
 }