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 <essob...@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; }