org.opensearch.common.settings.PropertyPlaceholder Maven / Gradle / Ivy
Show all versions of opensearch Show documentation
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.common.settings;
import org.opensearch.core.common.Strings;
import java.util.HashSet;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
/**
* Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
* {@code ${name}}. Using {@code PropertyPlaceholder} these placeholders can be substituted for
* user-supplied values.
*
* Values for substitution can be supplied using a {@link Properties} instance or using a
* {@link PlaceholderResolver}.
*
* @opensearch.internal
*/
class PropertyPlaceholder {
private final String placeholderPrefix;
private final String placeholderSuffix;
private final boolean ignoreUnresolvablePlaceholders;
/**
* Creates a new PropertyPlaceholderHelper
that uses the supplied prefix and suffix.
*
* @param placeholderPrefix the prefix that denotes the start of a placeholder.
* @param placeholderSuffix the suffix that denotes the end of a placeholder.
* @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should be ignored
* (true
) or cause an exception (false
).
*/
PropertyPlaceholder(String placeholderPrefix, String placeholderSuffix, boolean ignoreUnresolvablePlaceholders) {
this.placeholderPrefix = Objects.requireNonNull(placeholderPrefix);
this.placeholderSuffix = Objects.requireNonNull(placeholderSuffix);
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
/**
* Replaces all placeholders of format ${name}
with the value returned from the supplied {@link
* PlaceholderResolver}.
*
* @param value the value containing the placeholders to be replaced.
* @param placeholderResolver the PlaceholderResolver
to use for replacement.
* @return the supplied value with placeholders replaced inline.
* @throws NullPointerException if value is null
*/
String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Objects.requireNonNull(value);
return parseStringValue(value, placeholderResolver, new HashSet<>());
}
private String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, Set visitedPlaceholders) {
StringBuilder buf = new StringBuilder(strVal);
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(buf, startIndex);
if (endIndex != -1) {
String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
if (!visitedPlaceholders.add(placeholder)) {
throw new IllegalArgumentException("Circular placeholder reference '" + placeholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
int defaultValueIdx = placeholder.indexOf(':');
String defaultValue = null;
if (defaultValueIdx != -1) {
defaultValue = placeholder.substring(defaultValueIdx + 1);
placeholder = placeholder.substring(0, defaultValueIdx);
}
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null) {
propVal = defaultValue;
}
if (propVal == null && placeholderResolver.shouldIgnoreMissing(placeholder)) {
if (placeholderResolver.shouldRemoveMissingPlaceholder(placeholder)) {
propVal = "";
} else {
return strVal;
}
}
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
} else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
} else {
throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'");
}
visitedPlaceholders.remove(placeholder);
} else {
startIndex = -1;
}
}
return buf.toString();
}
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + this.placeholderPrefix.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (Strings.substringMatch(buf, index, this.placeholderSuffix)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + this.placeholderSuffix.length();
} else {
return index;
}
} else if (Strings.substringMatch(buf, index, this.placeholderPrefix)) {
withinNestedPlaceholder++;
index = index + this.placeholderPrefix.length();
} else {
index++;
}
}
return -1;
}
/**
* Strategy interface used to resolve replacement values for placeholders contained in Strings.
*
* @see PropertyPlaceholder
*
* @opensearch.internal
*/
interface PlaceholderResolver {
/**
* Resolves the supplied placeholder name into the replacement value.
*
* @param placeholderName the name of the placeholder to resolve.
* @return the replacement value or null
if no replacement is to be made.
*/
String resolvePlaceholder(String placeholderName);
boolean shouldIgnoreMissing(String placeholderName);
/**
* Allows for special handling for ignored missing placeholders that may be resolved elsewhere
*
* @param placeholderName the name of the placeholder to resolve.
* @return true if the placeholder should be replaced with a empty string
*/
boolean shouldRemoveMissingPlaceholder(String placeholderName);
}
}