blob: a89a8683a925ac319cac4fa207c9414ec4130677 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.tamaya.jsr382;
import org.apache.tamaya.TypeLiteral;
import javax.config.ConfigAccessor;
import javax.config.ConfigSnapshot;
import javax.config.spi.Converter;
import java.time.Duration;
import java.util.*;
/**
* Tamaya implementation type for the {@link ConfigAccessor} type.
*
* @param <T> the target type.
*/
class TamayaConfigAccessor<T> implements ConfigAccessor<T> {
private JavaConfigAdapter javaConfigAdapter;
private String name;
private Converter<T> customConverter;
private TypeLiteral<T> targetType;
private T typedDefaultValue;
private String stringDefaultValue;
private List<ConfigAccessor<String>> fallbackAccessors = new ArrayList<>();
private List<String> lookupSuffixes = new ArrayList<>();
private boolean evaluateVariables = true;
private long timeout = 0;
private T cachedValue;
/**
* Constructor.
*
* @param builder the builder, not null.
*/
TamayaConfigAccessor(TamayaConfigAccessorBuilder<T> builder) {
this.name = builder.name;
this.targetType = builder.targetType;
this.stringDefaultValue = builder.stringDefaultValue;
this.javaConfigAdapter = builder.configAdapter;
this.evaluateVariables = builder.evaluateVariables;
this.lookupSuffixes.addAll(builder.lookupSuffixes);
this.customConverter = builder.customConverter;
this.typedDefaultValue = builder.typedDefaultValue;
// What to do if the fallback accessors do not match, e.g. with collection types...
this.fallbackAccessors.addAll(builder.fallbackAccessors);
this.cachedValue = getValue();
}
/**
* Access the list of al current possible candidate keys to evaluate a value for the given accessor.
*
* @return the list of al current possible candidate keys, not null.
*/
public List<String> getCandidateKeys() {
List<String> keys = new ArrayList<>();
List<List<String>> listList = new ArrayList<>();
List<String> maxList = new ArrayList<>(lookupSuffixes);
while (!maxList.isEmpty()) {
listList.add(new ArrayList(maxList));
maxList.remove(0);
}
for (List<String> list : listList) {
keys.addAll(getSuffixKeys(list));
}
keys.add(getPropertyName());
return keys;
}
private List<String> getSuffixKeys(List<String> list) {
List<String> result = new ArrayList<>();
while (!list.isEmpty()) {
result.add(getPropertyName() + '.' + String.join(".", list));
if (list.size() > 1) {
list.remove(list.size() - 2);
} else {
list.remove(0);
}
}
return result;
}
@Override
public T getValue() {
if (this.timeout > System.currentTimeMillis()) {
return this.cachedValue;
}
T value = getValueInternal(name);
if (value == null) {
value = getValueInternalFromDefaults();
}
if (value == null) {
throw new IllegalArgumentException("No such value: " + name);
}
return value;
}
private T getValueInternal(String key) {
T value = null;
if (customConverter != null) {
String textVal = javaConfigAdapter.getConfiguration().getOrDefault(name, String.class, null);
if (textVal == null) {
textVal = stringDefaultValue;
}
if (textVal != null) {
value = customConverter.convert(textVal);
}
} else {
value = javaConfigAdapter.getConfiguration().getOrDefault(name, targetType, null);
}
return value;
}
private T getValueInternalFromDefaults() {
T value = null;
if (customConverter != null) {
String textVal = stringDefaultValue;
if (textVal != null) {
value = customConverter.convert(textVal);
}
}
// Should we also try to convert with the String default value and existing converters?
if (value == null) {
value = typedDefaultValue;
}
return value;
}
@Override
public T getValue(ConfigSnapshot configSnapshot) {
return ((TamayaConfigSnapshot) configSnapshot).getConfiguration()
.get(name, targetType);
}
@Override
public Optional<T> getOptionalValue(ConfigSnapshot configSnapshot) {
return Optional.ofNullable(((TamayaConfigSnapshot) configSnapshot).getConfiguration()
.getOrDefault(name, targetType, null));
}
@Override
public Optional<T> getOptionalValue() {
return Optional.empty();
}
@Override
public String getPropertyName() {
return this.name;
}
@Override
public String getResolvedPropertyName() {
return null;
}
@Override
public T getDefaultValue() {
return typedDefaultValue;
}
@Override
public String toString() {
return "TamayaConfigAccessor{" +
"javaConfigAdapter=" + javaConfigAdapter +
", name='" + name + '\'' +
", customConverter=" + customConverter +
", targetType=" + targetType +
", typedDefaultValue=" + typedDefaultValue +
", stringDefaultValue='" + stringDefaultValue + '\'' +
", fallbackAccessors=" + fallbackAccessors +
", lookupSuffixes=" + lookupSuffixes +
", evaluateVariables=" + evaluateVariables +
", timeout=" + timeout +
", cachedValue=" + cachedValue +
'}';
}
/**
* Implementation of the {@link javax.config.ConfigAccessor.Builder} class.
* @param <T> the type.
*/
static final class TamayaConfigAccessorBuilder<T> implements ConfigAccessor.Builder<T>{
private String name;
private JavaConfigAdapter configAdapter;
private Converter<T> customConverter;
private TypeLiteral<T> targetType;
private T typedDefaultValue;
private String stringDefaultValue;
private List<ConfigAccessor<String>> fallbackAccessors = new ArrayList<>();
private List<String> lookupSuffixes = new ArrayList<>();
private boolean evaluateVariables = true;
private long timeout = 0;
private T cachedValue;
private Duration cacheDuration;
/**
* Constructor.
*
* @param configAdapter the config, not null.
*/
TamayaConfigAccessorBuilder(JavaConfigAdapter configAdapter, String name, TypeLiteral<T> type) {
this.configAdapter = configAdapter;
this.name = name;
this.targetType = targetType;
}
@Override
public Builder<T> useConverter(Converter<T> converter) {
this.customConverter = Objects.requireNonNull(converter);
return this;
}
@Override
public Builder<T> withDefault(T value) {
this.typedDefaultValue = Objects.requireNonNull(value);
return this;
}
@Override
public Builder<T> withStringDefault(String value) {
this.stringDefaultValue = value;
return this;
}
@Override
public Builder<T> cacheFor(Duration duration) {
this.cacheDuration = Objects.requireNonNull(duration);
return this;
}
@Override
public Builder<T> evaluateVariables(boolean evaluateVariables) {
this.evaluateVariables = evaluateVariables;
return this;
}
@Override
public Builder<T> addLookupSuffix(String suffixValue) {
this.lookupSuffixes.add(Objects.requireNonNull(suffixValue));
return this;
}
@Override
public ConfigAccessor<T> build() {
return new TamayaConfigAccessor<T>(this);
}
}
}