From da3c1c55856ca0591a6d35acf5c9155d66e99a3f Mon Sep 17 00:00:00 2001 From: Sebastian Daschner Date: Thu, 2 May 2019 14:19:26 +0200 Subject: [PATCH 1/2] first thoughts on type-based configuration Signed-off-by: Sebastian Daschner --- .../java/javax/config/ComposedConfig.java | 252 ++++++++++++ .../main/asciidoc/composed-configuration.adoc | 375 ++++++++++++++++++ .../main/asciidoc/javaconfig-spec.asciidoc | 1 + 3 files changed, 628 insertions(+) create mode 100644 api/src/main/java/javax/config/ComposedConfig.java create mode 100644 spec/src/main/asciidoc/composed-configuration.adoc diff --git a/api/src/main/java/javax/config/ComposedConfig.java b/api/src/main/java/javax/config/ComposedConfig.java new file mode 100644 index 0000000..e67bc98 --- /dev/null +++ b/api/src/main/java/javax/config/ComposedConfig.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 + * + * 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 javax.config; + +import javax.config.inject.ConfigProperty; +import javax.enterprise.util.Nonbinding; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Specifies a configuration type as composed configuration. + * Composed configuration types comprise multiple, potentially hierarchical, configuration values. + * + *

Examples

+ * + *

Composed, coherent configuration

+ *

+ * The following example defines a coherent server socket configuration. + * + *

+ * @ComposedConfig
+ * public class SocketConfig {
+ *
+ *     @ConfigProperty(name = "name")
+ *     private String name;
+ *
+ *     @ConfigProperty(name = "protocol", defaultValue = "http")
+ *     private String protocolName;
+ *
+ *     @ConfigProperty(name = "port")
+ *     private int port;
+ *
+ *     // getters & setters
+ * }
+ * 
+ *

+ * The {@code SocketConfig} configuration can be retrieved like any other configured value, by programmatic lookup, or dependency injection: + * + *

+ * public class SomeBean {
+ *
+ *     @Inject
+ *     @ConfigProperty(name = "server.socket")
+ *     private SocketConfig socketConfig;
+ *
+ * }
+ * 
+ *

+ * The example will resolve the configuration values as follows, provided by the corresponding property keys: + * + *

+ * server.socket.name
+ * server.socket.protocol
+ * server.socket.port
+ * 
+ * + *

Implicit property resolution

+ *

+ * It's possible to omit the individual {@link ConfigProperty} annotations on the fields of the composed type. + * In this case the composed property keys are derived from the field names: + *

+ * + *

+ * public class SomeBean {
+ *
+ *     @Inject
+ *     @ConfigProperty(name = "server.socket")
+ *     private SocketConfig socketConfig;
+ *
+ *     @ComposedConfig
+ *     public static class SocketConfig {
+ *
+ *         private String name;
+ *         private String protocol;
+ *         private int port;
+ *
+ *         // getters & setters
+ *     }
+ * }
+ * 
+ * + *

+ * This example will result in the same configuration resolution as in the previous example, apart from the default value for {@code server.socket.protocol}. + *

+ * If the property keys differ from the field names, they can be overridden individually via the {@link ConfigProperty} annotation. + * The same is true for default values, as seen before. + *

+ * The {@link ConfigProperty} annotation can be annotated on fields as well as on methods. + * The latter is useful if interfaces instead of classes are defined as composed types. + * Per default, methods are not implicitly taken to resolve composed configuration properties. + * If both fields and methods are annotated within a single type, methods take precedence. + *

+ * See the following example for a composed interface configuration type. + *

+ * + *

+ * @ComposedConfig
+ * public interface SocketConfig {
+ *
+ *     @ConfigProperty(name = "name")
+ *     String name();
+ *
+ *     @ConfigProperty(name = "protocol", defaultValue = "http")
+ *     String protocolName();
+ *
+ *     @ConfigProperty(name = "port")
+ *     int getPort();
+ * }
+ * 
+ *

+ * This example will result in the same configuration as before. + * + *

Hierarchical type resolution

+ *

+ * The configuration properties of composed types are resolved hierarchically. + * That is, properties in composed types are implicitly considered as possible composed types themselves, as well. + * This allows developers to define complex configuration structures without repeating annotations. + *

+ * The types of composed properties are therefore resolved as possible composed configuration types, if no built-in, custom, or implicit converters are defined. + *

+ * The hierarchical resolution works both for implicitly resolved fields and explicitly annotated members. + * + *

+ * @ComposedConfig
+ * public class ServerConfig {
+ *
+ *     private String host;
+ *     private SocketConfig socket;
+ *
+ *     // getters & setters
+ *
+ *     public static class SocketConfig {
+ *         private String name;
+ *         private String protocol;
+ *         private int port;
+ *
+ *         // getters & setters
+ *     }
+ *
+ * }
+ * 
+ *

+ * If a {@code ServerConfig} configuration type is retrieved, the property keys are resolved as follows: + * + *

+ * public class SomeBean {
+ *
+ *     @Inject
+ *     @ConfigProperty(name = "server")
+ *     private ServerConfig serverConfig;
+ *
+ * }
+ * 
+ *

+ * This leads to: + * + *

+ * server.host
+ * server.socket.name
+ * server.socket.protocol
+ * server.socket.port
+ * 
+ *

+ * The property keys are resolved by the field names, or the names defined in {@link ConfigProperty}, respectively, and combined via dot ({@code .}). + *

+ * The example above is congruent with annotating {@code SocketConfig} with {@link ComposedConfig}, as well. + * + *

Collection resolution

+ *

+ * Composed configuration types also resolve collections and array types. + * + *

+ * @ComposedConfig
+ * public class MultiSocketServerConfig {
+ *
+ *     private String[] hosts;
+ *     private List sockets;
+ *
+ *     // getters & setters
+ *
+ *     public static class SocketConfig {
+ *         private String name;
+ *         private String protocol;
+ *         private int port;
+ *
+ *         // getters & setters
+ *     }
+ *
+ * }
+ * 
+ *

+ * If the {@code MultiSocketServerConfig} type is resolved by key {@code alternative-server}, it results in the following: + * + *

+ * server.hosts.0
+ * server.hosts.1
+ *
+ * server.sockets.0.name
+ * server.sockets.0.protocol
+ * server.sockets.1.name
+ * 
+ *

+ * Element types of collections and arrays are resolved by an implicit zero-based index, which is part of the resulting, combined property key. + *

+ * This collection resolution works for array types, and types that are assignable to {@link java.util.Collection}. + * For unordered collection types, e.g. {@link java.util.Set}, the order in which the configured elements will be retrieved is non-deterministic, despite the (zero-based) indexed key names. + *

+ * Similar to singular sub-types, the element types within the collection or array are resolved by potentially existent converters, and resolved recursively if no built-in, custom, or implicit converters are defined. + * + * @author Sebastian Daschner + */ +@Retention(RUNTIME) +@Target(TYPE) +public @interface ComposedConfig { + + /** + * Only valid for injection of dynamically readable values, e.g. {@code Provider}! + * + * @return {@code TimeUnit} for {@link #cacheFor()} + */ + @Nonbinding + TimeUnit cacheTimeUnit() default TimeUnit.SECONDS; + + /** + * Only valid for injection of dynamically readable values, e.g. {@code Provider}! + * + * @return how long should dynamic values be locally cached. Measured in {@link #cacheTimeUnit()}. + */ + @Nonbinding + long cacheFor() default 0L; +} diff --git a/spec/src/main/asciidoc/composed-configuration.adoc b/spec/src/main/asciidoc/composed-configuration.adoc new file mode 100644 index 0000000..321e5e7 --- /dev/null +++ b/spec/src/main/asciidoc/composed-configuration.adoc @@ -0,0 +1,375 @@ +// +// Copyright (c) 2016-2018 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// 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 +// +// 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. +// Contributors: +// Sebastian Daschner + +[[composed-configuration]] +== Composed Configuration + +Developers can specify composed configuration types in order to define multiple, coherent configuration values, and access them conveniently and consistently. +Composed configuration types, which are annotated with `@ComposedConfig`, comprise multiple, potentially hierarchical, configuration values. + +=== Usage + +See the following example that defines a coherent server socket configuration. + +[source,java] +---- +@ComposedConfig +public class SocketConfig { + + @ConfigProperty(name = "name") + private String name; + + @ConfigProperty(name = "protocol", defaultValue = "http") + private String protocolName; + + @ConfigProperty(name = "port") + private int port; + + // getters & setters +} +---- + +The `SocketConfig` configuration can be retrieved like any other configured value, by programmatic lookup, or dependency injection: + +[source,java] +---- +public class SomeBean { + + @Inject + @ConfigProperty(name = "server.socket") + private SocketConfig socketConfig; + +} +---- + +The example will resolve the configuration values as follows, provided by the corresponding property keys: + +[source,text] +---- +socketConfig.getName() -> server.socket.name +socketConfig.getProtocolName() -> server.socket.protocol (or "http" if undefined) +socketConfig.getPort() -> server.socket.port +---- + + +==== Implicit Property Resolution + +It's possible to omit the individual `@ConfigProperty` annotations on the fields of the composed type. +In this case the composed property keys are derived from the field names: + +[source,java] +---- +public class SomeBean { + + @Inject + @ConfigProperty(name = "server.socket") + private SocketConfig socketConfig; + + @ComposedConfig + public static class SocketConfig { + + private String name; + private String protocol; + private int port; + + // getters & setters + } +} +---- + +This example will result in the same configuration resolution as in the previous example, apart from the default value (`http`) for `server.socket.protocol`. + +If the property keys differ from the field names, they can be overridden individually via the `@ConfigProperty` annotation. +The same is true for default values, as seen before. + +The `@ConfigProperty` annotation can be annotated on fields as well as on methods. +The latter is useful if interfaces instead of classes are defined as composed types. +Per default, methods are not implicitly taken to resolve composed configuration properties. +If both fields and methods are annotated within a single type, methods take precedence. + +See the following example for a composed interface configuration type. + +[source,java] +---- +@ComposedConfig +public interface SocketConfig { + + @ConfigProperty(name = "name") + String name(); + + @ConfigProperty(name = "protocol", defaultValue = "http") + String protocolName(); + + @ConfigProperty(name = "port") + int getPort(); +} +---- + +Again, this example will result in the same configuration as before. + + +==== Hierarchical Type Resolution + +The configuration properties of composed types are resolved hierarchically. +That is, properties in composed types are implicitly considered as possible composed types themselves, as well. +This allows developers to define complex configuration structures without repeating annotations. + +The types of composed properties are therefore resolved as possible composed configuration types, if no built-in, custom, or implicit converters are defined. + +The hierarchical resolution works both for implicitly resolved fields and explicitly annotated members. + +[source,java] +---- +@ComposedConfig +public class ServerConfig { + + private String host; + private SocketConfig socket; + + // getters & setters + + public static class SocketConfig { + private String name; + private String protocol; + private int port; + + // getters & setters + } + +} +---- + +If a `ServerConfig` configuration type is retrieved, the property keys are resolved as follows: + +[source,java] +---- +public class SomeBean { + + @Inject + @ConfigProperty(name = "server") + private ServerConfig serverConfig; + +} +---- + +This leads to: + +[source,text] +---- +serverConfig.getHost() -> server.host +serverConfig.getSocket().getName() -> server.socket.name +serverConfig.getSocket().getProtocol() -> server.socket.protocol +serverConfig.getSocket().getPort() -> server.socket.port +---- + +The property keys are resolved by the field names, or the names defined in `@ConfigProperty`, respectively, and combined via dot (`.`). + +The example above is congruent with annotating `SocketConfig` with `@ComposedConfig`, as well. + + +==== Collection Resolution + +Composed configuration types also resolve collections and array types. + +[source,java] +---- +@ComposedConfig +public class MultiSocketServerConfig { + + private String[] hosts; + private List sockets; + + // getters & setters + + public static class SocketConfig { + private String name; + private String protocol; + private int port; + + // getters & setters + } + +} +---- + +If the `MultiSocketServerConfig` type is resolved by key `alternative-server`, it results in the following: + +[source,text] +---- +serverConfig.getHosts()[0] -> server.hosts.0 +serverConfig.getHosts()[1] -> server.hosts.1 +... +serverConfig.getSockets().get(0).getName() -> server.sockets.0.name +serverConfig.getSockets().get(0).getProtocol() -> server.sockets.0.protocol +serverConfig.getSockets().get(1).getName() -> server.sockets.1.name +... +---- + +Element types of collections and arrays are resolved by an implicit zero-based index, which is part of the resulting, combined property key. + +This collection resolution works for array types, and types that are assignable to `java.util.Collection`. +For unordered collection types, e.g. `java.util.Set`, the order in which the configured elements will be retrieved is non-deterministic, despite the (zero-based) indexed key names. + +Similar to singular sub-types, the element types within the collection or array are resolved by potentially existent converters, and resolved recursively if no built-in, custom, or implicit converters are defined. + +[[resolution]] +=== Resolution + +The following examines how the resolution for composed configuration values works. +This information is particularly interesting for implementors. + +1. The `Config` implementation detects whether the type of a retrieved configuration value is a composed configuration. +This is handled equally, whether the configuration is retrieved programmatically, or via dependency injection. +The configuration value type is considered a composed type if the type definition is annotated with `@ComposedConfig`. + +2. The retrieval of a composed value MUST be performed by a single config source at a time, in order of their defined priority. +Due to the potential hierarchical nature of composed configuration, the individual sources must define coherent configuration compositions. +Defining multiple parts of composed values in multiple config sources is not supported. +The config sources will override the whole composition of a composed configuration value individually. + +3. The individual, potentially hierarchical properties are resolved by the implementation by inspecting the composed configuration type definition. +The following order MUST be followed, while subsequent, colliding definitions might override the resolved field names. + - every non-static, non-final field that is not annotated with `@ConfigProperty`, with its declared field name as key suffix and field type as configured property type + - every non-static, non-final field that is annotated with `@ConfigProperty`, with the specified name as key suffix, optional default value and field type as configured property type + - every non-static, non-void method that is annotated with `@ConfigProperty`, with the specified name as key suffix, optional default value and method return type as configured property type + +4. The resolved properties are looked-up via the config source following the same mechanism as for any other configuration values, except the constraints mentioned in steps 5. and 6. +The configured keys used lookup the configuration values are concatenated as follows: + - the keys of all config properties in the hierarchy of the composed configuration value (see step 5.), individually joined by a dot (`.`) + - the key suffix derived from the defined property +For example, a composed configuration `serverConfig` with lookup key `server`, and property `socket` with identical implicit property key (`socket`), and property `name` with identical implicit property key (`name`) will be resolved as property key `server.socket.name`. + +5. Properties within the composed type are themselves resolved and inspected for composed types. +Unlike general configuration lookup, configured property types are considered as composed types, if no built-in, custom, or implicit converter is defined for them. +If no build-in, custom, or implicit converter could be resolved by the implementation, following the corresponding priorities, the configured type is considered a composed configuration and resolution is performed recursively, starting from step 3. +To ensure configuration consistency, implementations MUST resolve hierarchical sub-types using a single config source throughout the whole hierarchy for a single (root) composed configuration. +Unlike conventional configuration lookup, configured properties contained in composed types do not cause an error in case single configured properties are undefined (i.e. the computed property key doesn't lead to a configured value) in the config source, +In case a configured property is not defined, the value of the corresponding field is the default primitive value (e.g. `0` for `int`, `false` for `boolean`), `null` for reference types, `Optional.EMPTY` for `java.util.Optional` types, an empty array, or an empty collection of the corresponding collection type, respectively, depending on the property type. +In order to notify users of JSR 382 about configuration mismatches implementations SHOULD emit a warning if none of the resolved properties could be resolved within a (root) composed configuration value. + +6. Properties within the composed type are themselves resolved and inspected for collection types. +Following types are considered collection types: + - array types + - types assignable to `java.util.Collection` +The configured type comprised in the collection type are resolved recursively within the same config source. +Configured types are resolved as configuration values using built-in, custom, or implicit converters with their defined priority, or considered as composed types, if no built-in, custom, or implicit converter is defined for them. +The property keys of configured collection types are computed as follows: + - the property key of the collection type itself is computed following the rules described in step 4. + - every element of the collection is indexed with a zero-based integer index, which is concatenated separated by a dot (`.`). +For example, a collection type configuration `socketNames` of type `List` with property key `socket-names`, within a composed configuration type `server` with identical property key will be resolved as property keys `server.socket-names.0`, `server.socket-names.1`, etc. +Types within the collection that are themselves composed types compute their properties starting from the property key of the collection type and following the rules described in step 4. +Collection types that are not explicitly ordered compute the property keys following the same rules with non-deterministic ordering of the elements. +To ensure configuration consistency, implementations MUST resolve collection sub-types using a single config source throughout the whole hierarchy for a single (root) composed configuration. + + +=== Relationship to CDI Beans + +In order to define multiple, coherent configuration values conveniently, it's possible to inject conventional CDI scoped beans that themselves define configuration properties, without regarding composed configuration values. +An example looks as follows: + +[source,java] +---- +public class SomeBean { + + @Inject + private SocketConfig socketConfig; + +} +---- + +The `SomeBean` injects the dependent-scoped bean which comprised the coherent configuration values. + +[source,java] +---- +public class SocketConfig { + + @Inject + @ConfigProperty(name = "server.socket.name") + private String name; + + @Inject + @ConfigProperty(name = "server.socket.protocol", defaultValue = "http") + private String protocolName; + + @Inject + @ConfigProperty(name = "server.socket.port") + private int port; + + // accessor methods +} +---- + +This usage doesn't require composed configuration values. + +Developers might want to prefer to use composed configuration values in the following cases: + - complex hierarchies of configuration values must be realized + - collection types are used within the composed configuration hierarchy + - leaner syntax is preferred (possibility to omit `@Inject` and `@ConfigProperty` annotations + - duplication of configuration property key prefixes (e.g. `server.socket` should be avoided + - interface types should be used to define composed configuration types + - composed configuration types are defined by a third-party, or being reused, and thus can't or shouldn't be annotated with JSR 382 annotations + + +=== Hierarchical Configuration File Formats + +Config sources that are backed by file format with a hierarchical structure, such as XML, JSON, or YAML, SHOULD resolve the individual properties following the same rules as described in steps 4. and 6. of <> in order to end up with the same hierarchical semantics of the property keys. + +The following example illustrates this recommendation for a hypothetical config source that resolves YAML configuration files. + +Given the following YAML configuration structure: + +[source,yaml] +---- +server: + name: pet-1 + hosts: + - "foo.example.com" + - "bar.example.com" + sockets: + - name: http-default + protocol: http + port: 80 + - name: https-default + protocol: https + port: 443 +---- + +The YAML structure SHOULD result in configuration properties that are congruent with the following properties file definition: + +[source,text] +---- +server.name=pet-1 +server.hosts.0=foo.example.com +server.hosts.1=bar.example.com + +server.sockets.0.name=http-default +server.sockets.0.protocol=http +server.sockets.0.port=80 + +server.sockets.1.name=https-default +server.sockets.1.protocol=https +server.sockets.1.port=443 +---- + + +=== Decisions & Considerations (for EG members, to be removed) +- No prefix attribute added on `@ComposedConfig`, since it arguable doesn't provide much benefits. The injection point has to specify some property name anyway. Discussed on 2018-09-06. +- Starting with supporting to annotate `@ComposedConfig` on types only, i.e. no qualifier and no annotation on the injection point. Discussed on 2018-09-06. +- The programmatic `ConfigAccessor` API doesn't require a specific method (such as `composed()`), since the resolution currently requires the composed type to be annotated with `@ComposedConfig`. diff --git a/spec/src/main/asciidoc/javaconfig-spec.asciidoc b/spec/src/main/asciidoc/javaconfig-spec.asciidoc index 49cb0a2..0075d05 100644 --- a/spec/src/main/asciidoc/javaconfig-spec.asciidoc +++ b/spec/src/main/asciidoc/javaconfig-spec.asciidoc @@ -48,3 +48,4 @@ include::converters.asciidoc[] include::configaccessor.asciidoc[] +include::composed-configuration.asciidoc[] From 47caf1228c12ba9305da77a1b4daed98f9a78868 Mon Sep 17 00:00:00 2001 From: Sebastian Daschner Date: Fri, 3 May 2019 11:06:42 +0200 Subject: [PATCH 2/2] Simplified composed config - added alternative collection lookup to zero-based indexes - removed annotation - only interfaces allowed Signed-off-by: Sebastian Daschner --- .../java/javax/config/ComposedConfig.java | 252 ------------------ .../main/asciidoc/composed-configuration.adoc | 200 ++++++-------- 2 files changed, 77 insertions(+), 375 deletions(-) delete mode 100644 api/src/main/java/javax/config/ComposedConfig.java diff --git a/api/src/main/java/javax/config/ComposedConfig.java b/api/src/main/java/javax/config/ComposedConfig.java deleted file mode 100644 index e67bc98..0000000 --- a/api/src/main/java/javax/config/ComposedConfig.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * 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 - * - * 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 javax.config; - -import javax.config.inject.ConfigProperty; -import javax.enterprise.util.Nonbinding; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.util.concurrent.TimeUnit; - -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Specifies a configuration type as composed configuration. - * Composed configuration types comprise multiple, potentially hierarchical, configuration values. - * - *

Examples

- * - *

Composed, coherent configuration

- *

- * The following example defines a coherent server socket configuration. - * - *

- * @ComposedConfig
- * public class SocketConfig {
- *
- *     @ConfigProperty(name = "name")
- *     private String name;
- *
- *     @ConfigProperty(name = "protocol", defaultValue = "http")
- *     private String protocolName;
- *
- *     @ConfigProperty(name = "port")
- *     private int port;
- *
- *     // getters & setters
- * }
- * 
- *

- * The {@code SocketConfig} configuration can be retrieved like any other configured value, by programmatic lookup, or dependency injection: - * - *

- * public class SomeBean {
- *
- *     @Inject
- *     @ConfigProperty(name = "server.socket")
- *     private SocketConfig socketConfig;
- *
- * }
- * 
- *

- * The example will resolve the configuration values as follows, provided by the corresponding property keys: - * - *

- * server.socket.name
- * server.socket.protocol
- * server.socket.port
- * 
- * - *

Implicit property resolution

- *

- * It's possible to omit the individual {@link ConfigProperty} annotations on the fields of the composed type. - * In this case the composed property keys are derived from the field names: - *

- * - *

- * public class SomeBean {
- *
- *     @Inject
- *     @ConfigProperty(name = "server.socket")
- *     private SocketConfig socketConfig;
- *
- *     @ComposedConfig
- *     public static class SocketConfig {
- *
- *         private String name;
- *         private String protocol;
- *         private int port;
- *
- *         // getters & setters
- *     }
- * }
- * 
- * - *

- * This example will result in the same configuration resolution as in the previous example, apart from the default value for {@code server.socket.protocol}. - *

- * If the property keys differ from the field names, they can be overridden individually via the {@link ConfigProperty} annotation. - * The same is true for default values, as seen before. - *

- * The {@link ConfigProperty} annotation can be annotated on fields as well as on methods. - * The latter is useful if interfaces instead of classes are defined as composed types. - * Per default, methods are not implicitly taken to resolve composed configuration properties. - * If both fields and methods are annotated within a single type, methods take precedence. - *

- * See the following example for a composed interface configuration type. - *

- * - *

- * @ComposedConfig
- * public interface SocketConfig {
- *
- *     @ConfigProperty(name = "name")
- *     String name();
- *
- *     @ConfigProperty(name = "protocol", defaultValue = "http")
- *     String protocolName();
- *
- *     @ConfigProperty(name = "port")
- *     int getPort();
- * }
- * 
- *

- * This example will result in the same configuration as before. - * - *

Hierarchical type resolution

- *

- * The configuration properties of composed types are resolved hierarchically. - * That is, properties in composed types are implicitly considered as possible composed types themselves, as well. - * This allows developers to define complex configuration structures without repeating annotations. - *

- * The types of composed properties are therefore resolved as possible composed configuration types, if no built-in, custom, or implicit converters are defined. - *

- * The hierarchical resolution works both for implicitly resolved fields and explicitly annotated members. - * - *

- * @ComposedConfig
- * public class ServerConfig {
- *
- *     private String host;
- *     private SocketConfig socket;
- *
- *     // getters & setters
- *
- *     public static class SocketConfig {
- *         private String name;
- *         private String protocol;
- *         private int port;
- *
- *         // getters & setters
- *     }
- *
- * }
- * 
- *

- * If a {@code ServerConfig} configuration type is retrieved, the property keys are resolved as follows: - * - *

- * public class SomeBean {
- *
- *     @Inject
- *     @ConfigProperty(name = "server")
- *     private ServerConfig serverConfig;
- *
- * }
- * 
- *

- * This leads to: - * - *

- * server.host
- * server.socket.name
- * server.socket.protocol
- * server.socket.port
- * 
- *

- * The property keys are resolved by the field names, or the names defined in {@link ConfigProperty}, respectively, and combined via dot ({@code .}). - *

- * The example above is congruent with annotating {@code SocketConfig} with {@link ComposedConfig}, as well. - * - *

Collection resolution

- *

- * Composed configuration types also resolve collections and array types. - * - *

- * @ComposedConfig
- * public class MultiSocketServerConfig {
- *
- *     private String[] hosts;
- *     private List sockets;
- *
- *     // getters & setters
- *
- *     public static class SocketConfig {
- *         private String name;
- *         private String protocol;
- *         private int port;
- *
- *         // getters & setters
- *     }
- *
- * }
- * 
- *

- * If the {@code MultiSocketServerConfig} type is resolved by key {@code alternative-server}, it results in the following: - * - *

- * server.hosts.0
- * server.hosts.1
- *
- * server.sockets.0.name
- * server.sockets.0.protocol
- * server.sockets.1.name
- * 
- *

- * Element types of collections and arrays are resolved by an implicit zero-based index, which is part of the resulting, combined property key. - *

- * This collection resolution works for array types, and types that are assignable to {@link java.util.Collection}. - * For unordered collection types, e.g. {@link java.util.Set}, the order in which the configured elements will be retrieved is non-deterministic, despite the (zero-based) indexed key names. - *

- * Similar to singular sub-types, the element types within the collection or array are resolved by potentially existent converters, and resolved recursively if no built-in, custom, or implicit converters are defined. - * - * @author Sebastian Daschner - */ -@Retention(RUNTIME) -@Target(TYPE) -public @interface ComposedConfig { - - /** - * Only valid for injection of dynamically readable values, e.g. {@code Provider}! - * - * @return {@code TimeUnit} for {@link #cacheFor()} - */ - @Nonbinding - TimeUnit cacheTimeUnit() default TimeUnit.SECONDS; - - /** - * Only valid for injection of dynamically readable values, e.g. {@code Provider}! - * - * @return how long should dynamic values be locally cached. Measured in {@link #cacheTimeUnit()}. - */ - @Nonbinding - long cacheFor() default 0L; -} diff --git a/spec/src/main/asciidoc/composed-configuration.adoc b/spec/src/main/asciidoc/composed-configuration.adoc index 321e5e7..2394ca2 100644 --- a/spec/src/main/asciidoc/composed-configuration.adoc +++ b/spec/src/main/asciidoc/composed-configuration.adoc @@ -22,7 +22,7 @@ == Composed Configuration Developers can specify composed configuration types in order to define multiple, coherent configuration values, and access them conveniently and consistently. -Composed configuration types, which are annotated with `@ComposedConfig`, comprise multiple, potentially hierarchical, configuration values. +Composed configuration types, which are specified in an interface type, comprise multiple, potentially hierarchical, configuration values. === Usage @@ -30,19 +30,14 @@ See the following example that defines a coherent server socket configuration. [source,java] ---- -@ComposedConfig -public class SocketConfig { +public interface SocketConfig { - @ConfigProperty(name = "name") - private String name; + String name(); @ConfigProperty(name = "protocol", defaultValue = "http") - private String protocolName; - - @ConfigProperty(name = "port") - private int port; + String protocolName(); - // getters & setters + int getPort(); } ---- @@ -63,94 +58,34 @@ The example will resolve the configuration values as follows, provided by the co [source,text] ---- -socketConfig.getName() -> server.socket.name -socketConfig.getProtocolName() -> server.socket.protocol (or "http" if undefined) +socketConfig.name() -> server.socket.name +socketConfig.protocolName() -> server.socket.protocol (or "http" if undefined) socketConfig.getPort() -> server.socket.port ---- - -==== Implicit Property Resolution - -It's possible to omit the individual `@ConfigProperty` annotations on the fields of the composed type. -In this case the composed property keys are derived from the field names: - -[source,java] ----- -public class SomeBean { - - @Inject - @ConfigProperty(name = "server.socket") - private SocketConfig socketConfig; - - @ComposedConfig - public static class SocketConfig { - - private String name; - private String protocol; - private int port; - - // getters & setters - } -} ----- - -This example will result in the same configuration resolution as in the previous example, apart from the default value (`http`) for `server.socket.protocol`. - -If the property keys differ from the field names, they can be overridden individually via the `@ConfigProperty` annotation. -The same is true for default values, as seen before. - -The `@ConfigProperty` annotation can be annotated on fields as well as on methods. -The latter is useful if interfaces instead of classes are defined as composed types. -Per default, methods are not implicitly taken to resolve composed configuration properties. -If both fields and methods are annotated within a single type, methods take precedence. - -See the following example for a composed interface configuration type. - -[source,java] ----- -@ComposedConfig -public interface SocketConfig { - - @ConfigProperty(name = "name") - String name(); - - @ConfigProperty(name = "protocol", defaultValue = "http") - String protocolName(); - - @ConfigProperty(name = "port") - int getPort(); -} ----- - -Again, this example will result in the same configuration as before. +All public, non-static methods of the interface are resolved as configuration values of the composed configuration. +The configuration property names are derived implicitly by following the JavaBean naming standards (name of `get` methods), or alternatively the full name of the method. +Additionally, a property name can be overridden with the `name` attribute of the `@ConfigProperty` annotation. ==== Hierarchical Type Resolution The configuration properties of composed types are resolved hierarchically. -That is, properties in composed types are implicitly considered as possible composed types themselves, as well. -This allows developers to define complex configuration structures without repeating annotations. - -The types of composed properties are therefore resolved as possible composed configuration types, if no built-in, custom, or implicit converters are defined. - -The hierarchical resolution works both for implicitly resolved fields and explicitly annotated members. +That is, properties in composed types may be composed types themselves, as well. +This allows developers to define complex configuration structures without repeating types or annotations. [source,java] ---- -@ComposedConfig -public class ServerConfig { - - private String host; - private SocketConfig socket; +public interface ServerConfig { - // getters & setters + String host(); + SocketConfig socket(); - public static class SocketConfig { - private String name; - private String protocol; - private int port; - - // getters & setters + interface SocketConfig { + String name(); + @ConfigProperty(name = "protocol") + String protocolName(); + int port(); } } @@ -173,15 +108,13 @@ This leads to: [source,text] ---- -serverConfig.getHost() -> server.host -serverConfig.getSocket().getName() -> server.socket.name -serverConfig.getSocket().getProtocol() -> server.socket.protocol -serverConfig.getSocket().getPort() -> server.socket.port +serverConfig.host() -> server.host +serverConfig.socket().name() -> server.socket.name +serverConfig.socket().protocol() -> server.socket.protocol +serverConfig.socket().port() -> server.socket.port ---- -The property keys are resolved by the field names, or the names defined in `@ConfigProperty`, respectively, and combined via dot (`.`). - -The example above is congruent with annotating `SocketConfig` with `@ComposedConfig`, as well. +The property keys are resolved by the method names, or the names defined in `@ConfigProperty`, respectively, and combined via dot (`.`). ==== Collection Resolution @@ -190,20 +123,15 @@ Composed configuration types also resolve collections and array types. [source,java] ---- -@ComposedConfig -public class MultiSocketServerConfig { - - private String[] hosts; - private List sockets; - - // getters & setters +public interface MultiSocketServerConfig { - public static class SocketConfig { - private String name; - private String protocol; - private int port; + String[] hosts(); + List getSockets(); - // getters & setters + interface SocketConfig { + String name(); + String protocol(); + int port(); } } @@ -213,31 +141,49 @@ If the `MultiSocketServerConfig` type is resolved by key `alternative-server`, i [source,text] ---- -serverConfig.getHosts()[0] -> server.hosts.0 -serverConfig.getHosts()[1] -> server.hosts.1 +serverConfig.hosts()[0] -> alternative-server.hosts.0 +serverConfig.hosts()[1] -> alternative-server.hosts.1 ... -serverConfig.getSockets().get(0).getName() -> server.sockets.0.name -serverConfig.getSockets().get(0).getProtocol() -> server.sockets.0.protocol -serverConfig.getSockets().get(1).getName() -> server.sockets.1.name +serverConfig.getSockets().get(0).name() -> alternative-server.sockets.0.name +serverConfig.getSockets().get(0).protocol() -> alternative-server.sockets.0.protocol +serverConfig.getSockets().get(1).name() -> alternative-server.sockets.1.name ... ---- -Element types of collections and arrays are resolved by an implicit zero-based index, which is part of the resulting, combined property key. +Element types of collections and arrays can be resolved by an implicit zero-based index, which is part of the resulting, combined property key. This collection resolution works for array types, and types that are assignable to `java.util.Collection`. For unordered collection types, e.g. `java.util.Set`, the order in which the configured elements will be retrieved is non-deterministic, despite the (zero-based) indexed key names. Similar to singular sub-types, the element types within the collection or array are resolved by potentially existent converters, and resolved recursively if no built-in, custom, or implicit converters are defined. +In order to facilitate writing larger structures, collection elements can also be indexed by arbitrary names instead of zero-based indexes. +See the following example: + +[source,text] +---- +server.sockets.public.name=http +server.sockets.public.port=80 +server.sockets.alternative.name=http-alt +server.sockets.alternative.port=8080 +server.sockets.admin.name=http-admin +server.sockets.admin.port=9999 +---- + +The socket names `public`, `alternative`, and `admin` are used to avoid following strict ordering of numbers. +The `alternative` element, for example, can be removed without reordering the indexes of the other elements. +If the resulting collection type is ordered, the order of the resulting elements follow the alphabetical order of the provided names (`admin`, `alternative`, `public` in the example). + + [[resolution]] === Resolution The following examines how the resolution for composed configuration values works. -This information is particularly interesting for implementors. +This information is particularly interesting for implementers. 1. The `Config` implementation detects whether the type of a retrieved configuration value is a composed configuration. This is handled equally, whether the configuration is retrieved programmatically, or via dependency injection. -The configuration value type is considered a composed type if the type definition is annotated with `@ComposedConfig`. +The configuration value type is considered a composed type if the retrieved type is an interface and doesn't define a built-in, custom, or implicit converter. 2. The retrieval of a composed value MUST be performed by a single config source at a time, in order of their defined priority. Due to the potential hierarchical nature of composed configuration, the individual sources must define coherent configuration compositions. @@ -245,10 +191,10 @@ Defining multiple parts of composed values in multiple config sources is not sup The config sources will override the whole composition of a composed configuration value individually. 3. The individual, potentially hierarchical properties are resolved by the implementation by inspecting the composed configuration type definition. -The following order MUST be followed, while subsequent, colliding definitions might override the resolved field names. - - every non-static, non-final field that is not annotated with `@ConfigProperty`, with its declared field name as key suffix and field type as configured property type - - every non-static, non-final field that is annotated with `@ConfigProperty`, with the specified name as key suffix, optional default value and field type as configured property type - - every non-static, non-void method that is annotated with `@ConfigProperty`, with the specified name as key suffix, optional default value and method return type as configured property type +The following order MUST be followed, while subsequent, colliding definitions might override the resolved method names. + - every public, non-static method that is not annotated with `@ConfigProperty` that follows the JavaBean getter naming standard, with its declared method name with out the `get` prefix as key suffix and method return type as configured property type + - every public, non-static method that is not annotated with `@ConfigProperty`, with its declared method name as key suffix and method return type as configured property type + - every public, non-static method that is annotated with `@ConfigProperty`, with the annotation `name` as key suffix, optional default value and method return type as configured property type 4. The resolved properties are looked-up via the config source following the same mechanism as for any other configuration values, except the constraints mentioned in steps 5. and 6. The configured keys used lookup the configuration values are concatenated as follows: @@ -261,7 +207,7 @@ Unlike general configuration lookup, configured property types are considered as If no build-in, custom, or implicit converter could be resolved by the implementation, following the corresponding priorities, the configured type is considered a composed configuration and resolution is performed recursively, starting from step 3. To ensure configuration consistency, implementations MUST resolve hierarchical sub-types using a single config source throughout the whole hierarchy for a single (root) composed configuration. Unlike conventional configuration lookup, configured properties contained in composed types do not cause an error in case single configured properties are undefined (i.e. the computed property key doesn't lead to a configured value) in the config source, -In case a configured property is not defined, the value of the corresponding field is the default primitive value (e.g. `0` for `int`, `false` for `boolean`), `null` for reference types, `Optional.EMPTY` for `java.util.Optional` types, an empty array, or an empty collection of the corresponding collection type, respectively, depending on the property type. +In case a configured property is not defined, the value of the corresponding property is the default primitive value (e.g. `0` for `int`, `false` for `boolean`), `null` for reference types, `Optional.EMPTY` for `java.util.Optional` types, an empty array, or an empty collection of the corresponding collection type, respectively, depending on the property type. In order to notify users of JSR 382 about configuration mismatches implementations SHOULD emit a warning if none of the resolved properties could be resolved within a (root) composed configuration value. 6. Properties within the composed type are themselves resolved and inspected for collection types. @@ -277,6 +223,9 @@ For example, a collection type configuration `socketNames` of type `List Types within the collection that are themselves composed types compute their properties starting from the property key of the collection type and following the rules described in step 4. Collection types that are not explicitly ordered compute the property keys following the same rules with non-deterministic ordering of the elements. To ensure configuration consistency, implementations MUST resolve collection sub-types using a single config source throughout the whole hierarchy for a single (root) composed configuration. + - alternatively, the elements of the collection can be referred by arbitrary names instead of zero-based indexes. +The implementations will resolve and populate the collections accordingly. +Ordered collections, such as arrays or `List` types are resolved by sorting the specified elements by the provided element key names in alphabetical order. === Relationship to CDI Beans @@ -327,6 +276,17 @@ Developers might want to prefer to use composed configuration values in the foll - composed configuration types are defined by a third-party, or being reused, and thus can't or shouldn't be annotated with JSR 382 annotations +=== Consistency + +The retrieval of a composed value MUST be performed by a single config source at a time, in order of their defined priority. +Due to the potential hierarchical nature of composed configuration, the individual sources must define coherent configuration compositions. +Defining multiple parts of composed values in multiple config sources is not supported. +The config sources will override the whole composition of a composed configuration value individually. + +Additionally, the lookup of composed configuration values is not allowed to specify a `cacheFor` behavior. +The individual or composed values of retrieved composed configurations MUST not change, once the values are returned to the users. + + === Hierarchical Configuration File Formats Config sources that are backed by file format with a hierarchical structure, such as XML, JSON, or YAML, SHOULD resolve the individual properties following the same rules as described in steps 4. and 6. of <> in order to end up with the same hierarchical semantics of the property keys. @@ -367,9 +327,3 @@ server.sockets.1.name=https-default server.sockets.1.protocol=https server.sockets.1.port=443 ---- - - -=== Decisions & Considerations (for EG members, to be removed) -- No prefix attribute added on `@ComposedConfig`, since it arguable doesn't provide much benefits. The injection point has to specify some property name anyway. Discussed on 2018-09-06. -- Starting with supporting to annotate `@ComposedConfig` on types only, i.e. no qualifier and no annotation on the injection point. Discussed on 2018-09-06. -- The programmatic `ConfigAccessor` API doesn't require a specific method (such as `composed()`), since the resolution currently requires the composed type to be annotated with `@ComposedConfig`.