diff --git a/README.md b/README.md index fa7f74808..9c7576b87 100644 --- a/README.md +++ b/README.md @@ -572,6 +572,14 @@ Covers two areas related to Spring Web: - Custom error handlers. - Cooperation with Qute templating engine. +### `spring/spring-properties` +Exploratory testing for the `quarkus-spring-boot-properties` Quarkus extension. The application consists of a REST endpoint +with some different approaches to inject properties. + +Current limitations: +- Relaxing name convention is not supported and it won't be supported: https://github.com/quarkusio/quarkus/issues/12483 +- The annotation `@ConstructorBinding` is not supported yet: https://github.com/quarkusio/quarkus/issues/19364 + ### `infinispan-client` Verifies the way of the sharing cache by Datagrid operator and Infinispan cluster and data consistency after failures. diff --git a/pom.xml b/pom.xml index dd2038764..848d34bbb 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,7 @@ kamelet spring/spring-data spring/spring-web + spring/spring-properties logging/jboss cache/caffeine diff --git a/spring/spring-properties/pom.xml b/spring/spring-properties/pom.xml new file mode 100644 index 000000000..010d46d71 --- /dev/null +++ b/spring/spring-properties/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + io.quarkus.ts.qe + parent + 1.0.0-SNAPSHOT + ../.. + + spring-properties + jar + Quarkus QE TS: Spring: Properties + + + io.quarkus + quarkus-spring-boot-properties + + + io.quarkus + quarkus-spring-web + + + diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/CollectionsController.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/CollectionsController.java new file mode 100644 index 000000000..5bdc7ecf4 --- /dev/null +++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/CollectionsController.java @@ -0,0 +1,49 @@ +package io.quarkus.ts.spring.properties; + +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/collections") +public class CollectionsController { + + // Using `@Inject` instead of `@Autowired` to verify we can use both. + @Inject + ListWiringProperties listProperties; + + // Injecting maps is not supported. Reported in https://github.com/quarkusio/quarkus/issues/19366 + // @Autowired + // MapWiringProperties mapProperties; + + @GetMapping("/list/strings") + public String listOfStrings() { + return listProperties.strings.stream().collect(Collectors.joining(", ")); + } + + // Injecting lists with objects is not unsupported. Reported in https://github.com/quarkusio/quarkus/issues/19365 + // @GetMapping("/list/persons") + // public String listOfPersons() { + // return listProperties.persons.stream().map(Object::toString).collect(Collectors.joining(", ")); + // } + // + + // Injecting maps is not supported. Reported in https://github.com/quarkusio/quarkus/issues/19366 + // @GetMapping("/map/integers") + // public String mapOfIntegers() { + // return mapProperties.integers.entrySet().stream() + // .map(e -> e.getKey() + "=" + e.getValue()) + // .collect(Collectors.joining(", ")); + // } + // + // @GetMapping("/map/persons") + // public String mapOfPersons() { + // return mapProperties.persons.entrySet().stream() + // .map(e -> e.getKey() + "=" + e.getValue()) + // .collect(Collectors.joining(", ")); + // } +} diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingController.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingController.java new file mode 100644 index 000000000..79fc19db1 --- /dev/null +++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingController.java @@ -0,0 +1,39 @@ +package io.quarkus.ts.spring.properties; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/greeting") +public class GreetingController { + + @Autowired + GreetingProperties properties; + + @GetMapping("/text") + public String text() { + return properties.text; + } + + @GetMapping("/textWithDefault") + public String textWithDefault() { + return properties.textWithDefault; + } + + @GetMapping("/textPrivate") + public String textPrivate() { + return properties.getTextPrivate(); + } + + @GetMapping("/textOptional") + public String textOptional() { + return properties.textOptional.orElse("empty!"); + } + + @GetMapping("/message") + public String message() { + return properties.message.toString(); + } +} diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingProperties.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingProperties.java new file mode 100644 index 000000000..da68f7f40 --- /dev/null +++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/GreetingProperties.java @@ -0,0 +1,42 @@ +package io.quarkus.ts.spring.properties; + +import java.util.Optional; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("greeting") +public class GreetingProperties { + + // Cover private field using public getter/setter + private String textPrivate; + + // Cover optional fields + public Optional textOptional; + + // Cover direct field binding + public String text; + + // Cover field with defaults + public String textWithDefault = "Hola"; + + // Cover group fields + public NestedMessage message; + + public String getTextPrivate() { + return textPrivate; + } + + public void setTextPrivate(String textPrivate) { + this.textPrivate = textPrivate; + } + + public static class NestedMessage { + public String text; + public String person = "unknown"; + + @Override + public String toString() { + return text + " " + person + "!"; + } + } +} \ No newline at end of file diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ListWiringProperties.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ListWiringProperties.java new file mode 100644 index 000000000..3c0bc7077 --- /dev/null +++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ListWiringProperties.java @@ -0,0 +1,26 @@ +package io.quarkus.ts.spring.properties; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("lists") +public class ListWiringProperties { + + // Cover injection of lists of strings; + public List strings; + + // Inject of complex objects in lists is not supported: https://github.com/quarkusio/quarkus/issues/19365 + // // Cover injection of classes + // public List persons; + // + // public static class Person { + // public String name; + // public int age; + // + // @Override + // public String toString() { + // return String.format("person[%s:%s]", name, age); + // } + // } +} \ No newline at end of file diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/MapWiringProperties.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/MapWiringProperties.java new file mode 100644 index 000000000..70c42221c --- /dev/null +++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/MapWiringProperties.java @@ -0,0 +1,24 @@ +package io.quarkus.ts.spring.properties; + +import java.util.Map; + +// Injecting maps is not supported. Reported in https://github.com/quarkusio/quarkus/issues/19366 +// @ConfigurationProperties("maps") +public class MapWiringProperties { + + // Cover injection of maps of integers and strings; + public Map integers; + + // Cover injection of maps of string and classes + public Map persons; + + public static class Person { + public String name; + public int age; + + @Override + public String toString() { + return String.format("person[%s:%s]", name, age); + } + } +} \ No newline at end of file diff --git a/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ValueController.java b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ValueController.java new file mode 100644 index 000000000..36cd917c6 --- /dev/null +++ b/spring/spring-properties/src/main/java/io/quarkus/ts/spring/properties/ValueController.java @@ -0,0 +1,52 @@ +package io.quarkus.ts.spring.properties; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/values") +public class ValueController { + + @Value("${values.text}") + String fieldUsingValue; + + @Value("${values.list}") + String[] fieldUsingArray; + + // Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368" + // @Value("#{'${values.list}'.split(',')}") + // List fieldUsingList; + + // Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368" + // @Value("#{${values.map}}") + // Map fieldUsingMap; + + @GetMapping("/fieldUsingValue") + public String fieldUsingValue() { + return fieldUsingValue; + } + + @GetMapping("/fieldUsingArray") + public String fieldUsingArray() { + return Stream.of(fieldUsingArray).collect(Collectors.joining(", ")); + } + + // Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368" + // @GetMapping("/fieldUsingList") + // public String fieldUsingList() { + // return fieldUsingList.stream().collect(Collectors.joining(", ")); + // } + // + // @GetMapping("/fieldUsingMap") + // public String fieldUsingMap() { + // return fieldUsingMap.entrySet().stream() + // .map(e -> e.getKey() + ": " + e.getValue()) + // .collect(Collectors.joining(", ")); + // } + +} diff --git a/spring/spring-properties/src/main/resources/application.properties b/spring/spring-properties/src/main/resources/application.properties new file mode 100644 index 000000000..673914499 --- /dev/null +++ b/spring/spring-properties/src/main/resources/application.properties @@ -0,0 +1,26 @@ +# Basic Fields +greeting.text=hello +greeting.text-private=private hello! +greeting.message.text=Hola + +# List Fields +lists.strings[0]=Value 1 + +lists.persons[0].name=Sarah +lists.persons[0].age=19 +lists.persons[1].name=Terminator +lists.persons[1].age=999 + +# Map Fields +maps.integers.1=Value 1 +maps.integers.2=Value 2 + +maps.persons.character1.name=Sarah +maps.persons.character1.age=19 +maps.persons.character2.name=Terminator +maps.persons.character2.age=999 + +# Values +values.text=hello +values.list=A,B +values.map={key1: '1', key2: '2', key3: '3'} \ No newline at end of file diff --git a/spring/spring-properties/src/test/java/io/quarkus/ts/spring/properties/SpringPropertiesIT.java b/spring/spring-properties/src/test/java/io/quarkus/ts/spring/properties/SpringPropertiesIT.java new file mode 100644 index 000000000..7e95444a4 --- /dev/null +++ b/spring/spring-properties/src/test/java/io/quarkus/ts/spring/properties/SpringPropertiesIT.java @@ -0,0 +1,136 @@ +package io.quarkus.ts.spring.properties; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.services.QuarkusApplication; + +@QuarkusScenario +public class SpringPropertiesIT { + + @QuarkusApplication + static RestService app = new RestService(); + + @Test + public void shouldInjectPublicFieldFromConfigurationPropertiesIntoController() { + // "hello" is defined in application.properties + assertEquals("hello", getGreetingsWithPath("/text")); + } + + @Test + public void shouldInjectDefaultPublicFieldFromConfigurationPropertiesIntoController() { + // "hola" is the default value in GreetingProperties.java as there is no property defined yet + assertEquals("Hola", getGreetingsWithPath("/textWithDefault")); + + setApplicationProperty("greeting.text-with-default", "Alo!"); + assertEquals("Alo!", getGreetingsWithPath("/textWithDefault")); + } + + @Test + public void shouldInjectPrivateFieldFromConfigurationPropertiesIntoController() { + // "private hello!" is defined in application.properties + assertEquals("private hello!", getGreetingsWithPath("/textPrivate")); + } + + @Test + public void shouldInjectOptionalPublicFieldFromConfigurationPropertiesIntoController() { + // "empty" is returned when the property is not in application.properties + assertEquals("empty!", getGreetingsWithPath("/textOptional")); + + setApplicationProperty("greeting.text-optional", "Hi!"); + assertEquals("Hi!", getGreetingsWithPath("/textOptional")); + } + + @Test + public void shouldInjectGroupPublicFieldFromConfigurationPropertiesIntoController() { + // "Hola unknown!" is returned when the property person is not in application.properties + assertEquals("Hola unknown!", getGreetingsWithPath("/message")); + + setApplicationProperty("greeting.message.person", "Sarah"); + assertEquals("Hola Sarah!", getGreetingsWithPath("/message")); + } + + @Test + public void shouldInjectListsOfStringFromConfigurationProperties() { + // These values comes from the application.properties + assertEquals("Value 1", getCollectionsWithPath("/list/strings")); + + setApplicationProperty("lists.strings[1]", "Value 2"); + assertEquals("Value 1, Value 2", getCollectionsWithPath("/list/strings")); + } + + @Disabled("Inject of complex objects in lists is not supported: https://github.com/quarkusio/quarkus/issues/19365") + @Test + public void shouldInjectListsOfPersonFromConfigurationProperties() { + // These values comes from the application.properties + assertEquals("person[Sarah:19], person[Terminator:999]", getCollectionsWithPath("/list/persons")); + } + + @Disabled("Inject maps is not supported: https://github.com/quarkusio/quarkus/issues/19366") + @Test + public void shouldInjectMapsOfIntegerFromConfigurationProperties() { + // These values comes from the application.properties + assertEquals("1=Value 1, 2=Value 2", getCollectionsWithPath("/maps/integers")); + } + + @Disabled("Inject maps is not supported: https://github.com/quarkusio/quarkus/issues/19366") + @Test + public void shouldInjectMapsOfPersonFromConfigurationProperties() { + // These values comes from the application.properties + assertEquals("character1=person[Sarah:19], character2=person[Terminator:999]", + getCollectionsWithPath("/maps/persons")); + } + + @Test + public void shouldInjectFieldsUsingValueAnnotation() { + assertEquals("hello", getValuesWithPath("/fieldUsingValue")); + } + + @Test + public void shouldInjectArrayFieldsUsingValueAnnotation() { + assertEquals("A, B", getValuesWithPath("/fieldUsingArray")); + } + + @Disabled("Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368") + @Test + public void shouldInjectListFieldsUsingValueAnnotation() { + assertEquals("A, B", getValuesWithPath("/fieldUsingList")); + } + + @Disabled("Complex SpEL expresions is not supported: https://github.com/quarkusio/quarkus/issues/19368") + @Test + public void shouldInjectMapFieldsUsingValueAnnotation() { + assertEquals("key1: 1, key2: 2, key3: 3", getValuesWithPath("/fieldUsingMap")); + } + + private void setApplicationProperty(String name, String value) { + app.stop(); + app.withProperty(name, value); + app.start(); + } + + private String getValuesWithPath(String path) { + return get("/values" + path); + } + + private String getCollectionsWithPath(String path) { + return get("/collections" + path); + } + + private String getGreetingsWithPath(String path) { + return get("/greeting" + path); + } + + private String get(String path) { + return given().when().get(path) + .then() + .statusCode(HttpStatus.SC_OK) + .extract().asString(); + } +}