blob: 5c663f8e05cead7e5709814b3a970aca5fc213b3 [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.geronimo.config;
import javax.config.ConfigSnapshot;
import javax.config.ConfigAccessor;
import javax.config.spi.Converter;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.logging.Logger;
import javax.enterprise.inject.Typed;
/**
* @author <a href="mailto:struberg@apache.org">Mark Struberg</a>
*/
@Typed
public class ConfigValueImpl<T> implements ConfigAccessor<T>, ConfigAccessor.Builder<T> {
private static final Logger logger = Logger.getLogger(ConfigValueImpl.class.getName());
private final ConfigImpl config;
private String keyOriginal;
private String keyResolved;
private Class<?> configEntryType = String.class;
private List<Object> lookupChain;
private boolean evaluateVariables = true;
private long cacheTimeNs = -1;
private volatile long reloadAfter = -1;
private long lastReloadedAt = -1;
private T lastValue = null;
private T defaultValue;
private boolean withDefault;
/**
* Alternative Converter to be used instead of the default converter
*/
private Converter<T> converter;
public ConfigValueImpl(ConfigImpl config, String key, Class<T> type) {
this.config = config;
this.keyOriginal = key;
this.configEntryType = type;
}
@Override
public ConfigAccessor.Builder<T> withDefault(T value) {
defaultValue = value;
withDefault = true;
return this;
}
@Override
public ConfigAccessor.Builder<T> withStringDefault(String value) {
if (value == null || value.isEmpty()) {
throw new RuntimeException("Empty String or null supplied as string-default value for property "
+ keyOriginal);
}
value = replaceVariables(value);
defaultValue = convert(value);
withDefault = true;
return this;
}
@Override
public T getDefaultValue() {
return defaultValue;
}
@Override
public ConfigAccessor<T> build() {
return this;
}
@Override
public Builder<T> useConverter(Converter<T> converter) {
this.converter = converter;
return this;
}
@Override
public ConfigValueImpl<T> cacheFor(Duration timeUnit) {
this.cacheTimeNs = timeUnit.toNanos();
return this;
}
@Override
public ConfigValueImpl<T> evaluateVariables(boolean evaluateVariables) {
this.evaluateVariables = evaluateVariables;
return this;
}
public ConfigValueImpl<T> addLookupSuffix(String suffixValue) {
if (lookupChain == null) {
lookupChain = new ArrayList<>();
}
this.lookupChain.add(suffixValue);
return this;
}
public ConfigValueImpl<T> addLookupSuffix(ConfigAccessor<String> suffixAccessor) {
if (lookupChain == null) {
lookupChain = new ArrayList<>();
}
this.lookupChain.add(suffixAccessor);
return this;
}
@Override
public Optional<T> getOptionalValue() {
return Optional.ofNullable(get());
}
//X will later get added again @Override
/*X
public ConfigValueImpl<T> onChange(ConfigChanged valueChangeListener) {
this.valueChangeListener = valueChangeListener;
return this;
}
*/
//X @Override
public List<T> getValueList() {
String rawList = (String) get(false);
List<T> values = new ArrayList<T>();
StringBuilder sb = new StringBuilder(64);
for (int i= 0; i < rawList.length(); i++) {
char c = rawList.charAt(i);
if ('\\' == c) {
if (i == rawList.length()) {
throw new IllegalStateException("incorrect escaping of key " + keyOriginal + " value: " + rawList);
}
char nextChar = rawList.charAt(i+1);
if (nextChar == '\\') {
sb.append('\\');
}
else if (nextChar == ',') {
sb.append(',');
}
i++;
}
else if (',' == c) {
addListValue(values, sb);
}
else {
sb.append(c);
}
}
addListValue(values, sb);
return values;
}
private void addListValue(List<T> values, StringBuilder sb) {
String val = sb.toString().trim();
if (!val.isEmpty()) {
values.add(convert(val));
}
sb.setLength(0);
}
public T get() {
return get(true);
}
@Override
public T getValue() {
T val = get();
if (val == null) {
throw new NoSuchElementException("No config value present for key " + keyOriginal);
}
return val;
}
@Override
public Optional<T> getOptionalValue(ConfigSnapshot configSnapshot) {
return Optional.ofNullable(get());
}
@Override
public T getValue(ConfigSnapshot configSnapshot) {
ConfigSnapshotImpl snapshotImpl = (ConfigSnapshotImpl) configSnapshot;
if (!snapshotImpl.getConfigValues().containsKey(this))
{
throw new IllegalArgumentException("The TypedResolver for key " + getPropertyName() +
" does not belong the given ConfigSnapshot!");
}
return (T) snapshotImpl.getConfigValues().get(this);
}
private T get(boolean convert) {
long now = -1;
if (cacheTimeNs > 0)
{
now = System.nanoTime();
if (now <= reloadAfter)
{
// now check if anything in the underlying Config got changed
long lastCfgChange = config.getLastChanged();
if (lastCfgChange < lastReloadedAt)
{
return lastValue;
}
}
}
String valueStr = resolveStringValue();
if ((valueStr == null || valueStr.isEmpty()) && withDefault) {
return defaultValue;
}
T value;
/* TODO add array support
if (isList || isSet) {
value = splitAndConvertListValue(valueStr);
if (isSet) {
value = (T) new HashSet((List) value);
}
}
else {
*/
value = convert ? convert(valueStr) : (T) valueStr;
/*
}
*/
//X will later get added again
/*X
if (valueChangeListener != null && (value != null && !value.equals(lastValue) || (value == null && lastValue != null)) )
{
valueChangeListener.onValueChange(keyOriginal, lastValue, value);
}
*/
lastValue = value;
if (cacheTimeNs > 0)
{
reloadAfter = now + cacheTimeNs;
lastReloadedAt = now;
}
return value;
}
private String resolveStringValue() {
String value = null;
if (lookupChain != null) {
// first we resolve the value
List<String> suffixVals = new ArrayList<>();
for (Object suffix : lookupChain) {
if (suffix instanceof ConfigAccessor) {
suffixVals.add(((ConfigAccessor<String>) suffix).getValue());
}
else {
suffixVals.add((String) suffix);
}
}
// binary count down
for (int mask = (1 << suffixVals.size()) - 1; mask > 0; mask--) {
StringBuilder sb = new StringBuilder(keyOriginal);
for (int loc = 0; loc < suffixVals.size(); loc++) {
int bitPos = 1 << (suffixVals.size() - loc - 1);
if ((mask & bitPos) > 0) {
sb.append('.').append(suffixVals.get(loc));
}
}
value = config.getValue(sb.toString());
if (value != null && value.length() > 0) {
keyResolved = sb.toString();
break;
}
}
}
if (value == null) {
value = config.getValue(keyOriginal);
this.keyResolved = keyOriginal;
}
if (evaluateVariables && value != null)
{
value = replaceVariables(value);
}
return value;
}
private String replaceVariables(String value)
{
// recursively resolve any ${varName} in the value
int startVar = 0;
while ((startVar = value.indexOf("${", startVar)) >= 0)
{
int endVar = value.indexOf("}", startVar);
if (endVar <= 0)
{
break;
}
String varName = value.substring(startVar + 2, endVar);
if (varName.isEmpty())
{
break;
}
String variableValue = config.access(varName, String.class).evaluateVariables(true).build().getValue();
if (variableValue != null)
{
value = value.replace("${" + varName + "}", variableValue);
}
startVar++;
}
return value;
}
//X @Override
public String getPropertyName() {
return keyOriginal;
}
//X @Override
public String getResolvedPropertyName() {
return keyResolved;
}
private T convert(String value) {
if (converter != null) {
return converter.convert(value);
}
if (String.class == configEntryType) {
return (T) value;
}
return (T) config.convert(value, configEntryType);
}
private T splitAndConvertListValue(String valueStr) {
if (valueStr == null) {
return null;
}
List list = new ArrayList();
StringBuilder currentValue = new StringBuilder();
int length = valueStr.length();
for (int i = 0; i < length; i++) {
char c = valueStr.charAt(i);
if (c == '\\') {
if (i < length - 1) {
char nextC = valueStr.charAt(i + 1);
currentValue.append(nextC);
i++;
}
}
else if (c == ',') {
String trimedVal = currentValue.toString().trim();
if (trimedVal.length() > 0) {
list.add(convert(trimedVal));
}
currentValue.setLength(0);
}
else {
currentValue.append(c);
}
}
String trimedVal = currentValue.toString().trim();
if (trimedVal.length() > 0) {
list.add(convert(trimedVal));
}
return (T) list;
}
}