io.helidon.config.objectmapping.Value Maven / Gradle / Ivy
Show all versions of helidon-config-object-mapping Show documentation
/*
* Copyright (c) 2018, 2021 Oracle and/or its affiliates.
*
* 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 io.helidon.config.objectmapping;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.function.Supplier;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation used to customize behaviour of JavaBean deserialization support.
*
* The first option for generic Config to JavaBean deserialization works just with class with no-parameter constructor.
* Each JavaBean property value is then set by value mapped from appropriate configuration node.
* Each public setter method and public non-final fields are taken as JavaBean properties that will be set.
* The deserialization process is applied recursively on each property.
*
* Use {@link Transient} annotation to exclude setter or field from set of processed JavaBean properties.
*
* By default JavaBean property name is used as config key to {@link io.helidon.config.Config#get(String) get} configuration node.
* The config key can be customized by {@link #key()} attribute. Use the annotation on public setter or public field.
* Annotation on method has precedence over annotation used on field. The second one is ignored.
*
* If the appropriate configuration node does not exist it is possible to specify default value:
*
* - {@link #withDefaultSupplier()} - instance of supplier class is used to get default value of target type; or
* - {@link #withDefault()} - default value in {@code String} form that will be mapped to target type
* by associated config mapping function
*
* In case of both default attributes are set the {@code withDefaultSupplier} is used
* and {@code withDefault} is ignored.
*
* public class AppConfig {
* private String greeting;
* private int pageSize;
* private List{@literal } range;
*
* public AppConfig() { // {@literal <1>}
* }
*
* public void setGreeting(String greeting) { // {@literal <2>}
* this.greeting = greeting;
* }
*
* {@literal @}Value(key = "page-size", withDefault = "10") // {@literal <3>}
* public void setPageSize(int pageSize) {
* this.pageSize = pageSize;
* }
*
* {@literal @}Value(withDefaultSupplier = DefaultRangeSupplier.class) // {@literal <4>}
* public void setRange(List{@literal } basicRange) {
* this.range = basicRange;
* }
*
* //...
*
* public static class DefaultRangeSupplier // {@literal <5>}
* implements Supplier{@literal >} {
* {@literal @}Override
* public List{@literal } get() {
* return List.of(0, 10);
* }
* }
* }
*
*
* - public no-parameter constructor;
* - property {@code greeting} is not customized; will be set from config node with {@code greeting} key, if exists;
* - property {@code pageSize} customizes key of config node to {@code page-size};
* if the config node does not exist, value {@code "10"} will be mapped to {@code int};
*
* - property {@code range} will be set from config node with same {@code range} key;
* if the config node does not exist, {@code DefaultRangeSupplier} instance will be used to get default value;
*
* - {@code DefaultRangeSupplier} is used to supply {@code List
} value.
*
*
* The second option is to provide factory public static method {@code from} with parameters set from configuration.
* Or public "factory" constructor with parameters can be used too.
*
* public class AppConfig {
* private final String greeting;
* private final int pageSize;
* private final List{@literal } basicRange;
*
* private AppConfig(String greeting, int pageSize, List{@literal } basicRange) {
* this.greeting = greeting;
* this.pageSize = pageSize;
* this.basicRange = basicRange;
* }
*
* //...
*
* // FACTORY METHOD
* public static AppConfig create({@literal @}Value(key = "greeting", withDefault = "Hi")
* String greeting,
* {@literal @}Value(key = "page-size", withDefault = "10")
* int pageSize,
* {@literal @}Value(key = "basic-range",
* withDefaultSupplier = DefaultBasicRangeSupplier.class)
* List{@literal } basicRange) {
* return new AppConfig(greeting, pageSize, basicRange);
* }
* }
*
*
* The third option is to provide Builder accessible by public static {@code builder()} method.
* The Builder instances is initialized via public setters or fields, similar to the first deserialization option.
* Finally, Builder has {@code build()} method that creates new instances of a bean.
*
* public class AppConfig {
* private final String greeting;
* private final int pageSize;
* private final List{@literal } basicRange;
*
* private AppConfig(String greeting, int pageSize, List{@literal } basicRange) {
* this.greeting = greeting;
* this.pageSize = pageSize;
* this.basicRange = basicRange;
* }
*
* // BUILDER METHOD
* public static Builder builder() {
* return new Builder();
* }
*
* public static class Builder {
* private String greeting;
* private int pageSize;
* private List{@literal } basicRange;
*
* private Builder() {
* }
*
* {@literal @}Value(withDefault = "Hi")
* public void setGreeting(String greeting) {
* this.greeting = greeting;
* }
*
* {@literal @}Value(key = "page-size", withDefault = "10")
* public void setPageSize(int pageSize) {
* this.pageSize = pageSize;
* }
*
* {@literal @}Value(key = "basic-range",
* withDefaultSupplier = DefaultBasicRangeSupplier.class)
* public void setBasicRange(List{@literal } basicRange) {
* this.basicRange = basicRange;
* }
*
* // BUILD METHOD
* public AppConfig build() {
* return new AppConfig(greeting, pageSize, basicRange);
* }
* }
* }
*
*
* Configuration example:
*
{@code
* {
* "app": {
* "greeting": "Hello",
* "page-size": 20,
* "range": [ -20, 20 ]
* }
* }
* }
* Getting {@code app} config node as {@code AppConfig} instance:
* {@code
* AppConfig appConfig = config.get("app").as(AppConfig.class);
* assert appConfig.getGreeting().equals("Hello");
* assert appConfig.getPageSize() == 20;
* assert appConfig.getRange().get(0) == -20;
* assert appConfig.getRange().get(1) == 20;
* }
* In this case default values where not used because JSON contains all expected nodes.
*
* The annotation cannot be applied on same JavaBean property together with {@link Transient}.
*
* @see Transient
*/
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER})
public @interface Value {
/**
* Specifies a key of configuration node to be used to set JavaBean property value from.
*
* If not specified original JavaBean property name is used.
*
* @return config property key
*/
String key() default "";
/**
* Specifies default value in form of single String value
* that will be used to set JavaBean property value in case configuration does not contain a config node
* of appropriate config key.
*
* In case {@link #withDefaultSupplier} is also used current value is ignored.
*
* @return single default value that will be converted into target type
*/
String withDefault() default None.VALUE;
/**
* Specifies supplier of default value
* that will be used to set JavaBean property value in case configuration does not contain config node
* of appropriate config key.
*
* Default value is used in case appropriate config value is not set.
* In case {@link #withDefault} is also used this one has higher priority and will be used.
*
* @return supplier that will provide default value in target type
*/
Class extends Supplier>> withDefaultSupplier() default None.class;
/**
* Class that represents not-set default values.
*/
interface None extends Supplier {
/**
* Value to use when a {@code null} value should be the default.
*/
String VALUE = "io.helidon.config:default=null";
@Override
default Void get() {
return null;
}
}
}