diff --git a/spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/AppBootSchemaVersion.java b/spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/AppBootSchemaVersion.java
new file mode 100644
index 0000000000..e1c8d6d63b
--- /dev/null
+++ b/spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/AppBootSchemaVersion.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 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.core;
+
+import java.util.Arrays;
+
+/**
+ * Defines the possible schema versions that currently map to Spring {@code "Boot"}. A registered application can only support one schema version.
+ *
+ *
Each value defines the supported Spring Boot version that represents the changes in the schemas or Spring Batch and Task.
+ *
+ * @author Chris Bono
+ * @author Corneil du Plessis
+ */
+public enum AppBootSchemaVersion {
+
+ BOOT2("2"),
+ BOOT3("3");
+
+ private String bootVersion;
+
+ AppBootSchemaVersion(String bootVersion) {
+ this.bootVersion = bootVersion;
+ }
+
+ public static AppBootSchemaVersion defaultVersion() {
+ return BOOT2;
+ }
+
+ public static AppBootSchemaVersion fromBootVersion(String bootVersion) {
+ return Arrays.stream(AppBootSchemaVersion.values())
+ .filter((bv) -> bv.bootVersion.equals(bootVersion))
+ .findFirst().orElseThrow(() -> new IllegalArgumentException("Invalid AppBootSchemaVersion: " + bootVersion));
+ }
+
+ public String getBootVersion() {
+ return this.bootVersion;
+ }
+
+ @Override
+ public String toString() {
+ return "AppBootVersion{bootVersion='" + this.bootVersion + "'}";
+ }
+}
diff --git a/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/AppBootSchemaVersionTests.java b/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/AppBootSchemaVersionTests.java
new file mode 100644
index 0000000000..5332812713
--- /dev/null
+++ b/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/AppBootSchemaVersionTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 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.core;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Unit tests for {@link AppBootSchemaVersion}.
+ *
+ * @author Chris Bono
+ * @author Corneil du Plessis
+ */
+public class AppBootSchemaVersionTests {
+
+ @Test
+ void bootVersion2() {
+ assertThat(AppBootSchemaVersion.BOOT2.getBootVersion()).isEqualTo("2");
+ }
+
+ @Test
+ void bootVersion3() {
+ assertThat(AppBootSchemaVersion.BOOT3.getBootVersion()).isEqualTo("3");
+ }
+
+ @Test
+ void fromBootVersionWithValidValues() {
+ assertThat(AppBootSchemaVersion.fromBootVersion("2")).isEqualTo(AppBootSchemaVersion.BOOT2);
+ assertThat(AppBootSchemaVersion.fromBootVersion("3")).isEqualTo(AppBootSchemaVersion.BOOT3);
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ @ValueSource(strings = { "Boot2", "boot2", "BOOT2", "foo", "Boot3", "boot3", "BOOT3" })
+ void fromBootVersionWithInvalidValues(String invalidBootVersion) {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> AppBootSchemaVersion.fromBootVersion(invalidBootVersion))
+ .withMessage("Invalid AppBootSchemaVersion: %s", invalidBootVersion);
+ }
+}
diff --git a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/AppRegistryOperations.java b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/AppRegistryOperations.java
index b89878e760..95bcb956cc 100644
--- a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/AppRegistryOperations.java
+++ b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/AppRegistryOperations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2019 the original author or authors.
+ * Copyright 2015-2023 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.
@@ -18,6 +18,7 @@
import java.util.Properties;
+import org.springframework.cloud.dataflow.core.AppBootSchemaVersion;
import org.springframework.cloud.dataflow.core.ApplicationType;
import org.springframework.cloud.dataflow.rest.resource.AppRegistrationResource;
import org.springframework.cloud.dataflow.rest.resource.DetailedAppRegistrationResource;
@@ -32,6 +33,7 @@
* @author Patrick Peralta
* @author Mark Fisher
* @author Chris Schaefer
+ * @author Chris Bono
*/
public interface AppRegistryOperations {
@@ -81,9 +83,24 @@ public interface AppRegistryOperations {
* @param metadataUri URI for the application metadata artifact
* @param force if {@code true}, overwrites a pre-existing registration
* @return the new app registration
+ * @deprecated in favor of {@link #register(String, ApplicationType, AppBootSchemaVersion, String, String, boolean)}
*/
+ @Deprecated
AppRegistrationResource register(String name, ApplicationType type, String uri, String metadataUri, boolean force);
+ /**
+ * Register an application name, type, and boot version with its Maven coordinates.
+ *
+ * @param name application name
+ * @param type application type
+ * @param bootVersion application boot version
+ * @param uri URI for the application artifact
+ * @param metadataUri URI for the application metadata artifact
+ * @param force if {@code true}, overwrites a pre-existing registration
+ * @return the new app registration
+ */
+ AppRegistrationResource register(String name, ApplicationType type, AppBootSchemaVersion bootVersion, String uri, String metadataUri, boolean force);
+
/**
* Register an application name, type and version with its Maven coordinates.
*
@@ -94,10 +111,27 @@ public interface AppRegistryOperations {
* @param metadataUri URI for the application metadata artifact
* @param force if {@code true}, overwrites a pre-existing registration
* @return the new app registration
+ * @deprecated in favor of {@link #register(String, ApplicationType, AppBootSchemaVersion, String, String, String, boolean)}
*/
+ @Deprecated
AppRegistrationResource register(String name, ApplicationType type, String version, String uri,
String metadataUri, boolean force);
+ /**
+ * Register an application name, type, boot version, and version with its Maven coordinates.
+ *
+ * @param name application name
+ * @param type application type
+ * @param bootVersion application boot version
+ * @param version application version
+ * @param uri URI for the application artifact
+ * @param metadataUri URI for the application metadata artifact
+ * @param force if {@code true}, overwrites a pre-existing registration
+ * @return the new app registration
+ */
+ AppRegistrationResource register(String name, ApplicationType type, AppBootSchemaVersion bootVersion, String version, String uri,
+ String metadataUri, boolean force);
+
/**
* Unregister an application name and type.
*
diff --git a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/AppRegistryTemplate.java b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/AppRegistryTemplate.java
index 96ce270cd5..44966a2f06 100644
--- a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/AppRegistryTemplate.java
+++ b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/AppRegistryTemplate.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2019 the original author or authors.
+ * Copyright 2015-2023 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.
@@ -18,6 +18,7 @@
import java.util.Properties;
+import org.springframework.cloud.dataflow.core.AppBootSchemaVersion;
import org.springframework.cloud.dataflow.core.ApplicationType;
import org.springframework.cloud.dataflow.rest.resource.AppRegistrationResource;
import org.springframework.cloud.dataflow.rest.resource.DetailedAppRegistrationResource;
@@ -40,6 +41,7 @@
* @author Patrick Peralta
* @author Christian Tzolov
* @author Chris Schaefer
+ * @author Chris Bono
*/
public class AppRegistryTemplate implements AppRegistryOperations {
/**
@@ -112,15 +114,14 @@ public DetailedAppRegistrationResource info(String name, ApplicationType type, S
}
@Override
- public AppRegistrationResource register(String name, ApplicationType type, String uri, String metadataUri,
- boolean force) {
- MultiValueMap values = new LinkedMultiValueMap();
- values.add("uri", uri);
- if (metadataUri != null) {
- values.add("metadata-uri", metadataUri);
- }
- values.add("force", Boolean.toString(force));
+ public AppRegistrationResource register(String name, ApplicationType type, String uri, String metadataUri, boolean force) {
+ return register(name, type, (AppBootSchemaVersion) null, uri, metadataUri, force);
+ }
+ @Override
+ public AppRegistrationResource register(String name, ApplicationType type, AppBootSchemaVersion bootVersion,
+ String uri, String metadataUri, boolean force) {
+ MultiValueMap values = valuesForRegisterPost(bootVersion, uri, metadataUri, force);
return restTemplate.postForObject(appsLink.getHref() + "/{type}/{name}", values,
AppRegistrationResource.class, type, name);
}
@@ -128,15 +129,29 @@ public AppRegistrationResource register(String name, ApplicationType type, Strin
@Override
public AppRegistrationResource register(String name, ApplicationType type, String version, String uri,
String metadataUri, boolean force) {
+ return this.register(name, type, null, version, uri, metadataUri, force);
+ }
+
+ @Override
+ public AppRegistrationResource register(String name, ApplicationType type, AppBootSchemaVersion bootVersion,
+ String version, String uri, String metadataUri, boolean force) {
+ MultiValueMap values = valuesForRegisterPost(bootVersion, uri, metadataUri, force);
+ return restTemplate.postForObject(appsLink.getHref() + "/{type}/{name}/{version}", values,
+ AppRegistrationResource.class, type, name, version);
+ }
+
+ private MultiValueMap valuesForRegisterPost(AppBootSchemaVersion bootVersion, String uri,
+ String metadataUri, boolean force) {
MultiValueMap values = new LinkedMultiValueMap<>();
values.add("uri", uri);
if (metadataUri != null) {
values.add("metadata-uri", metadataUri);
}
+ if (bootVersion != null) {
+ values.add("bootVersion", bootVersion.getBootVersion());
+ }
values.add("force", Boolean.toString(force));
-
- return restTemplate.postForObject(appsLink.getHref() + "/{type}/{name}/{version}", values,
- AppRegistrationResource.class, type, name, version);
+ return values;
}
@Override
diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/web/WebConfiguration.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/web/WebConfiguration.java
index f5ab03cc76..db93744889 100644
--- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/web/WebConfiguration.java
+++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/web/WebConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2022 the original author or authors.
+ * Copyright 2015-2023 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.
@@ -34,12 +34,15 @@
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
+import org.springframework.cloud.dataflow.core.AppBootSchemaVersion;
import org.springframework.cloud.dataflow.rest.support.jackson.ISO8601DateFormatWithMilliSeconds;
import org.springframework.cloud.dataflow.rest.support.jackson.Jackson2DataflowModule;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.format.FormatterRegistry;
import org.springframework.hateoas.server.core.DefaultLinkRelationProvider;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
@@ -58,6 +61,8 @@
* @author Christian Tzolov
* @author David Turanski
* @author Michael Wirth
+ * @author Chris Bono
+ * @author Corneil du Plessis
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@@ -92,6 +97,11 @@ public WebMvcConfigurer configurer() {
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
+
+ @Override
+ public void addFormatters(FormatterRegistry registry) {
+ registry.addConverter(new AppBootVersionConverter());
+ }
};
}
@@ -133,4 +143,13 @@ public void onApplicationEvent(ContextClosedEvent event) {
this.longTaskSample = null;
}
}
+
+ static class AppBootVersionConverter implements Converter {
+
+ @Override
+ public AppBootSchemaVersion convert(String value) {
+ return value != null ? AppBootSchemaVersion.fromBootVersion(value) : null;
+ }
+ }
+
}
diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java
index 02bc84fdba..54b0242a38 100644
--- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java
+++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AppRegistryController.java
@@ -35,6 +35,7 @@
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.cloud.dataflow.configuration.metadata.ApplicationConfigurationMetadataResolver;
+import org.springframework.cloud.dataflow.core.AppBootSchemaVersion;
import org.springframework.cloud.dataflow.core.AppRegistration;
import org.springframework.cloud.dataflow.core.ApplicationType;
import org.springframework.cloud.dataflow.core.StreamAppDefinition;
@@ -221,17 +222,21 @@ else if (entry.getKey().equals("outbound")) {
* @param type module type
* @param name module name
* @param version module version
+ * @param bootVersion module boot version or {@code null} to use the default
* @param uri URI for the module artifact (e.g. {@literal maven://group:artifact:version})
* @param metadataUri URI for the metadata artifact
* @param force if {@code true}, overwrites a pre-existing registration
*/
@RequestMapping(value = "/{type}/{name}/{version:.+}", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
- public void register(@PathVariable("type") ApplicationType type, @PathVariable("name") String name,
+ public void register(
+ @PathVariable("type") ApplicationType type,
+ @PathVariable("name") String name,
@PathVariable("version") String version,
- @RequestParam("uri") String uri, @RequestParam(name = "metadata-uri", required = false) String metadataUri,
+ @RequestParam(name = "bootVersion", required = false) AppBootSchemaVersion bootVersion,
+ @RequestParam("uri") String uri,
+ @RequestParam(name = "metadata-uri", required = false) String metadataUri,
@RequestParam(value = "force", defaultValue = "false") boolean force) {
-
validateApplicationName(name);
appRegistryService.validate(appRegistryService.getDefaultApp(name, type), uri, version);
AppRegistration previous = appRegistryService.find(name, type, version);
@@ -251,11 +256,15 @@ public void register(@PathVariable("type") ApplicationType type, @PathVariable("
@Deprecated
@RequestMapping(value = "/{type}/{name}", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
- public void register(@PathVariable("type") ApplicationType type, @PathVariable("name") String name,
- @RequestParam("uri") String uri, @RequestParam(name = "metadata-uri", required = false) String metadataUri,
+ public void register(
+ @PathVariable("type") ApplicationType type,
+ @PathVariable("name") String name,
+ @RequestParam(name = "bootVersion", required = false) AppBootSchemaVersion bootVersion,
+ @RequestParam("uri") String uri,
+ @RequestParam(name = "metadata-uri", required = false) String metadataUri,
@RequestParam(value = "force", defaultValue = "false") boolean force) {
String version = this.appRegistryService.getResourceVersion(uri);
- this.register(type, name, version, uri, metadataUri, force);
+ this.register(type, name, version, bootVersion, uri, metadataUri, force);
}
/**
diff --git a/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java b/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java
index 71ce505bea..0965453ffb 100644
--- a/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java
+++ b/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/AppRegistryCommands.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2022 the original author or authors.
+ * Copyright 2018-2023 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.
@@ -26,6 +26,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
+import org.springframework.cloud.dataflow.core.AppBootSchemaVersion;
import org.springframework.cloud.dataflow.core.ApplicationType;
import org.springframework.cloud.dataflow.rest.client.AppRegistryOperations;
import org.springframework.cloud.dataflow.rest.resource.AppRegistrationResource;
@@ -241,13 +242,13 @@ public String defaultApplication(
public String register(
@ShellOption(value = { "", "--name" }, help = "the name for the registered application") String name,
@ShellOption(help = "the type for the registered application", valueProvider = EnumValueProvider.class) ApplicationType type,
+ @ShellOption(value = { "-bv", "--bootVersion" }, help = "the boot version to use for the registered application", defaultValue = ShellOption.NULL) AppBootSchemaVersion bootVersion,
@ShellOption(help = "URI for the application artifact") String uri,
- @ShellOption(value = { "--metadata-uri", "--metadataUri"}, help = "Metadata URI for the application artifact", defaultValue = ShellOption.NULL) String metadataUri,
+ @ShellOption(value = { "-m", "--metadata-uri", "--metadataUri"}, help = "Metadata URI for the application artifact", defaultValue = ShellOption.NULL) String metadataUri,
@ShellOption(help = "force update if application is already registered (only if not in use)", defaultValue = "false") boolean force) {
-
- appRegistryOperations().register(name, type, uri, metadataUri, force);
-
- return String.format(("Successfully registered application '%s:%s'"), type, name);
+ appRegistryOperations().register(name, type, bootVersion, uri, metadataUri, force);
+ return String.format(("Successfully registered application '%s:%s%s"), type, name,
+ bootVersion == null ? "" : " (boot " + bootVersion.getBootVersion() + ")");
}
@ShellMethod(key = LIST_APPLICATIONS, value = "List all registered applications")
diff --git a/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/converter/AppBootVersionConverter.java b/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/converter/AppBootVersionConverter.java
new file mode 100644
index 0000000000..4a86609bde
--- /dev/null
+++ b/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/converter/AppBootVersionConverter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015-2022 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.shell.converter;
+
+import org.springframework.cloud.dataflow.core.AppBootSchemaVersion;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+/**
+ * Converts strings to {@link AppBootSchemaVersion}
+ *
+ * @author Chris Bono
+ */
+@Component
+public class AppBootVersionConverter implements Converter {
+
+ @Override
+ public AppBootSchemaVersion convert(String value) {
+ return value != null ? AppBootSchemaVersion.fromBootVersion(value) : null;
+ }
+}
diff --git a/spring-cloud-dataflow-shell-core/src/test/java/org/springframework/cloud/dataflow/shell/ShellCommandsTests.java b/spring-cloud-dataflow-shell-core/src/test/java/org/springframework/cloud/dataflow/shell/ShellCommandsTests.java
index 5c7c91e649..5b42207bf2 100644
--- a/spring-cloud-dataflow-shell-core/src/test/java/org/springframework/cloud/dataflow/shell/ShellCommandsTests.java
+++ b/spring-cloud-dataflow-shell-core/src/test/java/org/springframework/cloud/dataflow/shell/ShellCommandsTests.java
@@ -69,6 +69,7 @@ public void unregisterAll() {
@Test
public void testSingleFileCommand() {
String commandFiles = toAbsolutePaths("commands/registerTask_timestamp.txt");
+ // TODO add boot 3 checks
assertThat(runShell(commandFiles)).isTrue();
assertAppExists("timestamp", ApplicationType.task);
}