diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MetadataAggregationMojo.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MetadataAggregationMojo.java index 2e19b0122..75a931017 100644 --- a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MetadataAggregationMojo.java +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/main/java/org/springframework/cloud/dataflow/app/plugin/MetadataAggregationMojo.java @@ -134,7 +134,7 @@ public class MetadataAggregationMojo extends AbstractMojo { private final JsonMarshaller jsonMarshaller = new JsonMarshaller(); public void execute() throws MojoExecutionException { - Result result = new Result(gatherConfigurationMetadata(null), gatherVisibleMetadata()); + Result result = new Result(gatherConfigurationMetadata(null), gatherVisibleMetadata(), gatherOptionGroupsMetadata()); produceArtifact(result); if (storeFilteredMetadata) { @@ -332,6 +332,46 @@ else if (properties.containsKey(SPRING_CLOUD_STREAM_FUNCTION_DEFINITION)) { return visible; } + /** + * Read all existing option groups metadata from this project runtime dependencies and merge them in a single object. + */ + /*default*/ Properties gatherOptionGroupsMetadata() throws MojoExecutionException { + Properties optionGroups = new Properties(); + try { + for (String path : mavenProject.getRuntimeClasspathElements()) { + if (Files.isDirectory(Paths.get(path))) { + Path optionGroupsPath = Paths.get(path, "META-INF", SPRING_CLOUD_DATAFLOW_OPTION_GROUPS_PROPERTIES); + File optionGroupsFile = optionGroupsPath.toFile(); + if (optionGroupsFile.canRead()) { + try (InputStream is = new FileInputStream(optionGroupsFile)) { + Properties properties = new Properties(); + properties.load(is); + getLog().debug("Loading option groups from " + path); + optionGroups.putAll(properties); + } + } + } + else { + try (ZipFile zipFile = new ZipFile(new File(path))) { + ZipEntry entry = zipFile.getEntry("META-INF/" + SPRING_CLOUD_DATAFLOW_OPTION_GROUPS_PROPERTIES); + if (entry != null) { + try (InputStream inputStream = zipFile.getInputStream(entry)) { + Properties properties = new Properties(); + properties.load(inputStream); + getLog().debug("Loading option groups from " + path); + optionGroups.putAll(properties); + } + } + } + } + } + } + catch (Exception e) { + throw new MojoExecutionException("Exception trying to read option groups metadata from dependencies of project", e); + } + return optionGroups; + } + /*default*/ ConfigurationMetadata gatherConfigurationMetadata(MetadataFilter metadataFilters) throws MojoExecutionException { ConfigurationMetadata metadata = new ConfigurationMetadata(); @@ -502,11 +542,11 @@ private void mergeCommaDelimitedValue(Properties currentProperties, Properties n entry = new ZipEntry("META-INF/" + SPRING_CLOUD_DATAFLOW_PORT_MAPPING_PROPERTIES); jos.putNextEntry(entry); + result.getPortMappingProperties().store(jos, "Describes visible port mapping properties for this app"); entry = new ZipEntry("META-INF/" + SPRING_CLOUD_DATAFLOW_OPTION_GROUPS_PROPERTIES); jos.putNextEntry(entry); - - result.getPortMappingProperties().store(jos, "Describes visible port mapping properties for this app"); + result.getOptionGroupsProperties().store(jos, "Describes option groups for this app"); getLog().info(String.format("Attaching %s to current project", output.getCanonicalPath())); projectHelper.attachArtifact(mavenProject, output, classifier); @@ -630,6 +670,8 @@ public String toString() { private final Properties visible; + private final Properties optionGroups; + private Properties getPortMappingProperties() { Properties portMappingProperties = new Properties(); visible.entrySet().stream() @@ -638,9 +680,14 @@ private Properties getPortMappingProperties() { return portMappingProperties; } - private Result(ConfigurationMetadata metadata, Properties visible) { + private Properties getOptionGroupsProperties() { + return optionGroups; + } + + Result(ConfigurationMetadata metadata, Properties visible, Properties optionGroups) { this.metadata = metadata; this.visible = visible; + this.optionGroups = optionGroups; } } } diff --git a/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/OptionGroupsTest.java b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/OptionGroupsTest.java new file mode 100644 index 000000000..7be6667c4 --- /dev/null +++ b/spring-cloud-dataflow-apps-plugin/spring-cloud-dataflow-apps-metadata-plugin/src/test/java/org/springframework/cloud/dataflow/app/plugin/OptionGroupsTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2018-2025 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.cloud.dataflow.app.plugin; + +import java.io.File; +import java.io.InputStream; +import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectHelper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.cloud.dataflow.app.plugin.MetadataAggregationMojo.CONFIGURATION_PROPERTIES_INBOUND_PORTS; +import static org.springframework.cloud.dataflow.app.plugin.MetadataAggregationMojo.SPRING_CLOUD_DATAFLOW_OPTION_GROUPS_PROPERTIES; + +/** + * @author Max Brauer + */ +class OptionGroupsTest { + + @TempDir + File tempDir; + + @Test + void shouldWriteOptionGroupsToMetadataJar() throws Exception { + MetadataAggregationMojo mojo = new MetadataAggregationMojo(); + MavenProject project = createMockMavenProject(); + setField(mojo, "mavenProject", project); + setField(mojo, "projectHelper", mock(MavenProjectHelper.class)); + setField(mojo, "classifier", "metadata"); + + ConfigurationMetadata metadata = new ConfigurationMetadata(); + + Properties ports = new Properties(); // no ports + + Properties optionGroups = new Properties(); + optionGroups.setProperty("com.vmware.tanzu.dataflow.configuration-properties.option-groups.common", "prop1,prop2,prop3"); + optionGroups.setProperty("com.vmware.tanzu.dataflow.configuration-properties.option-groups.readers.filereader", "reader.prop1,reader.prop2"); + optionGroups.setProperty("com.vmware.tanzu.dataflow.configuration-properties.option-groups.writers.filewriter", "writer.prop1,writer.prop2,writer.prop3"); + + MetadataAggregationMojo.Result result = new MetadataAggregationMojo.Result(metadata, ports, optionGroups); + + mojo.produceArtifact(result); + + File output = new File(tempDir, "target/test-artifact-1.0.0-SNAPSHOT-metadata.jar"); + assertThat(output).exists(); + + try (ZipFile zipFile = new ZipFile(output)) { + ZipEntry optionGroupsEntry = zipFile.getEntry("META-INF/" + SPRING_CLOUD_DATAFLOW_OPTION_GROUPS_PROPERTIES); + assertThat(optionGroupsEntry).isNotNull(); + + Properties optionGroupsProps = new Properties(); + try (InputStream is = zipFile.getInputStream(optionGroupsEntry)) { + optionGroupsProps.load(is); + } + assertThat(optionGroupsProps) + .as("Option groups file should contain option groups properties") + .containsKey("com.vmware.tanzu.dataflow.configuration-properties.option-groups.common") + .containsKey("com.vmware.tanzu.dataflow.configuration-properties.option-groups.readers.filereader") + .containsKey("com.vmware.tanzu.dataflow.configuration-properties.option-groups.writers.filewriter"); + assertThat(optionGroupsProps.getProperty("com.vmware.tanzu.dataflow.configuration-properties.option-groups.common")) + .isEqualTo("prop1,prop2,prop3"); + } + } + + @Test + void shouldWriteEmptyOptionGroupsToMetadataJar() throws Exception { + MetadataAggregationMojo mojo = new MetadataAggregationMojo(); + MavenProject project = createMockMavenProject(); + setField(mojo, "mavenProject", project); + setField(mojo, "projectHelper", mock(MavenProjectHelper.class)); + setField(mojo, "classifier", "metadata"); + + ConfigurationMetadata metadata = new ConfigurationMetadata(); + + Properties ports = new Properties(); // no ports + Properties optionGroups = new Properties(); // no option groups + + MetadataAggregationMojo.Result result = new MetadataAggregationMojo.Result(metadata, ports, optionGroups); + + mojo.produceArtifact(result); + + File output = new File(tempDir, "target/test-artifact-1.0.0-SNAPSHOT-metadata.jar"); + assertThat(output).exists(); + + try (ZipFile zipFile = new ZipFile(output)) { + ZipEntry optionGroupsEntry = zipFile.getEntry("META-INF/" + SPRING_CLOUD_DATAFLOW_OPTION_GROUPS_PROPERTIES); + assertThat(optionGroupsEntry) + .as("Option groups file should exist even when empty") + .isNotNull(); + + Properties optionGroupsProps = new Properties(); + try (InputStream is = zipFile.getInputStream(optionGroupsEntry)) { + optionGroupsProps.load(is); + } + assertThat(optionGroupsProps) + .as("Option groups should be empty when no groups provided") + .isEmpty(); + } + } + + private MavenProject createMockMavenProject() { + MavenProject project = mock(MavenProject.class); + File targetDir = new File(tempDir, "target"); + targetDir.mkdirs(); + when(project.getBasedir()).thenReturn(tempDir); + when(project.getArtifactId()).thenReturn("test-artifact"); + when(project.getVersion()).thenReturn("1.0.0-SNAPSHOT"); + return project; + } + + private void setField(Object target, String fieldName, Object value) throws Exception { + java.lang.reflect.Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } +}