io.micronaut.context.env.DefaultPropertyPlaceholderResolver Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original authors
*
* 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
*
* https://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.micronaut.context.env;
import io.micronaut.context.exceptions.ConfigurationException;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.value.PropertyResolver;
import io.micronaut.core.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The default {@link PropertyPlaceholderResolver}.
*
* @author graemerocher
* @since 1.0
*/
public class DefaultPropertyPlaceholderResolver implements PropertyPlaceholderResolver {
/**
* Prefix for placeholder in properties.
*/
public static final String PREFIX = "${";
/**
* Suffix for placeholder in properties.
*/
public static final String SUFFIX = "}";
private static final Pattern ESCAPE_SEQUENCE = Pattern.compile("(.+)?:`([^`]+?)`");
private static final char COLON = ':';
private final PropertyResolver environment;
private final ConversionService> conversionService;
private final String prefix;
/**
* @param environment The property resolver for the environment
* @param conversionService The conversion service
*/
public DefaultPropertyPlaceholderResolver(PropertyResolver environment, ConversionService conversionService) {
this.environment = environment;
this.conversionService = conversionService;
this.prefix = PREFIX;
}
@Override
public String getPrefix() {
return this.prefix;
}
@Override
public Optional resolvePlaceholders(String str) {
try {
return Optional.of(resolveRequiredPlaceholders(str));
} catch (ConfigurationException e) {
return Optional.empty();
}
}
@Override
public String resolveRequiredPlaceholders(String str) throws ConfigurationException {
List segments = buildSegments(str);
StringBuilder value = new StringBuilder();
for (Segment segment: segments) {
value.append(segment.getValue(String.class));
}
return value.toString();
}
@Override
public T resolveRequiredPlaceholder(String str, Class type) throws ConfigurationException {
List segments = buildSegments(str);
if (segments.size() == 1) {
return segments.get(0).getValue(type);
} else {
throw new ConfigurationException("Cannot convert a multi segment placeholder to a specified type");
}
}
/**
* Split a placeholder value into logic segments.
*
* @param str The placeholder
* @return The list of segments
*/
public List buildSegments(String str) {
List segments = new ArrayList<>();
String value = str;
int i = value.indexOf(PREFIX);
while (i > -1) {
//the text before the prefix
if (i > 0) {
String rawSegment = value.substring(0, i);
segments.add(new RawSegment(rawSegment));
}
//everything after the prefix
value = value.substring(i + PREFIX.length());
int suffixIdx = value.indexOf(SUFFIX);
if (suffixIdx > -1) {
String expr = value.substring(0, suffixIdx).trim();
segments.add(new PlaceholderSegment(expr));
if (value.length() > suffixIdx) {
value = value.substring(suffixIdx + SUFFIX.length());
}
} else {
throw new ConfigurationException("Incomplete placeholder definitions detected: " + str);
}
i = value.indexOf(PREFIX);
}
if (value.length() > 0) {
segments.add(new RawSegment(value));
}
return segments;
}
/**
* Resolves a single expression.
*
* @param context The context of the expression
* @param expression The expression
* @param type The class
* @param The type the expression should be converted to
* @return The resolved and converted expression
*/
@Nullable
protected T resolveExpression(String context, String expression, Class type) {
if (environment.containsProperty(expression)) {
return environment.getProperty(expression, type)
.orElseThrow(() ->
new ConfigurationException("Could not resolve expression: [" + expression + "] in placeholder ${" + context + "}"));
}
if (NameUtils.isEnvironmentName(expression)) {
String envVar = System.getenv(expression);
if (StringUtils.isNotEmpty(envVar)) {
return conversionService.convert(envVar, type)
.orElseThrow(() ->
new ConfigurationException("Could not resolve expression: [" + expression + "] in placeholder ${" + context + "}"));
}
}
return null;
}
/**
* A segment of placeholder resolution.
*
* @author James Kleeh
* @since 1.1.0
*/
public interface Segment {
/**
* Returns the value of a given segment converted to
* the provided type.
*
* @param type The class
* @param The type to convert the value to
* @return The converted value
* @throws ConfigurationException If any error occurs
*/
T getValue(Class type) throws ConfigurationException;
}
/**
* A segment that represents static text.
*
* @author James Kleeh
* @since 1.1.0
*/
public class RawSegment implements Segment {
private final String text;
/**
* Default constructor.
*
* @param text The static text
*/
RawSegment(String text) {
this.text = text;
}
@Override
public T getValue(Class type) throws ConfigurationException {
if (type.isInstance(text)) {
return (T) text;
} else {
return conversionService.convert(text, type)
.orElseThrow(() ->
new ConfigurationException("Could not convert: [" + text + "] to the required type: [" + type.getName() + "]"));
}
}
}
/**
* A segment that represents one or more expressions
* that should be searched for in the environment.
*
* @author James Kleeh
* @since 1.1.0
*/
public class PlaceholderSegment implements Segment {
private final String placeholder;
private final List expressions = new ArrayList<>();
private String defaultValue;
/**
* Default constructor.
*
* @param placeholder The placeholder value without
* any prefix or suffix
*/
PlaceholderSegment(String placeholder) {
this.placeholder = placeholder;
findExpressions(placeholder);
}
/**
* @return The list of expressions that may be looked
* up in the environment
*/
public List getExpressions() {
return Collections.unmodifiableList(expressions);
}
@Override
public T getValue(Class type) throws ConfigurationException {
for (String expression: expressions) {
T value = resolveExpression(placeholder, expression, type);
if (value != null) {
return value;
}
}
if (defaultValue != null) {
return conversionService.convert(defaultValue, type)
.orElseThrow(() ->
new ConfigurationException(String.format("Could not convert default value [%s] in placeholder ${%s}", defaultValue, placeholder)));
} else {
throw new ConfigurationException("Could not resolve placeholder ${" + placeholder + "}");
}
}
private void findExpressions(String placeholder) {
String defaultValue = null;
String expression;
Matcher matcher = ESCAPE_SEQUENCE.matcher(placeholder);
boolean escaped = false;
if (matcher.find()) {
defaultValue = matcher.group(2);
expression = matcher.group(1);
escaped = true;
} else {
int j = placeholder.indexOf(COLON);
if (j > -1) {
defaultValue = placeholder.substring(j + 1);
expression = placeholder.substring(0, j);
} else {
expression = placeholder;
}
}
expressions.add(expression);
if (defaultValue != null) {
if (!escaped && (ESCAPE_SEQUENCE.matcher(defaultValue).find() || defaultValue.indexOf(COLON) > -1)) {
findExpressions(defaultValue);
} else {
this.defaultValue = defaultValue;
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy