refactor property placeholder resolution, check BytesStreamOutput for length limit
This commit is contained in:
parent
8d157d9192
commit
418524b427
6 changed files with 167 additions and 152 deletions
|
@ -4,6 +4,8 @@ image:https://api.travis-ci.org/xbib/content.svg[title="Build status", link="htt
|
||||||
image:https://img.shields.io/sonar/http/nemo.sonarqube.com/org.xbib%3Acontent/coverage.svg?style=flat-square[title="Coverage", link="https://sonarqube.com/dashboard/index?id=org.xbib%3Acontent"]
|
image:https://img.shields.io/sonar/http/nemo.sonarqube.com/org.xbib%3Acontent/coverage.svg?style=flat-square[title="Coverage", link="https://sonarqube.com/dashboard/index?id=org.xbib%3Acontent"]
|
||||||
image:https://maven-badges.herokuapp.com/maven-central/org.xbib/content/badge.svg[title="Maven Central", link="http://search.maven.org/#search%7Cga%7C1%7Cxbib%20content"]
|
image:https://maven-badges.herokuapp.com/maven-central/org.xbib/content/badge.svg[title="Maven Central", link="http://search.maven.org/#search%7Cga%7C1%7Cxbib%20content"]
|
||||||
image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[title="Apache License 2.0", link="https://opensource.org/licenses/Apache-2.0"]
|
image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[title="Apache License 2.0", link="https://opensource.org/licenses/Apache-2.0"]
|
||||||
|
image:https://img.shields.io/twitter/url/https/twitter.com/xbib.svg?style=social&label=Follow%20%40xbib[title="Twitter", link="https://twitter.com/xbib"]
|
||||||
|
image:https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif[title="PayPal", link="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=GVHFQYZ9WZ8HG"]
|
||||||
|
|
||||||
This is a Java library for processing structured data ("content") in the most popular formats, such as
|
This is a Java library for processing structured data ("content") in the most popular formats, such as
|
||||||
JSON, SMILE-JSON, YAML, XML, CSV, and also semantic descriptions in RDF (N-Triples, Turtle, RDF/XML).
|
JSON, SMILE-JSON, YAML, XML, CSV, and also semantic descriptions in RDF (N-Triples, Turtle, RDF/XML).
|
||||||
|
|
|
@ -14,7 +14,7 @@ ext {
|
||||||
allprojects {
|
allprojects {
|
||||||
|
|
||||||
group = 'org.xbib'
|
group = 'org.xbib'
|
||||||
version = '1.0.5'
|
version = '1.0.6'
|
||||||
|
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'maven'
|
apply plugin: 'maven'
|
||||||
|
|
|
@ -96,6 +96,9 @@ public class BytesStreamOutput extends OutputStream {
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ((long)count + length > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("overflow, stream output larger than " + Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
int newcount = count + length;
|
int newcount = count + length;
|
||||||
if (newcount > buf.length) {
|
if (newcount > buf.length) {
|
||||||
buf = Arrays.copyOf(buf, oversize(newcount));
|
buf = Arrays.copyOf(buf, oversize(newcount));
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.xbib.content.settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy interface used to resolve replacement values for placeholders contained in Strings.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public 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 <code>null</code> if no replacement is to be made.
|
||||||
|
*/
|
||||||
|
String resolvePlaceholder(String placeholderName);
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
package org.xbib.content.settings;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class PropertyPlaceholder {
|
||||||
|
|
||||||
|
private final String placeholderPrefix;
|
||||||
|
|
||||||
|
private final String placeholderSuffix;
|
||||||
|
|
||||||
|
private final boolean ignoreUnresolvablePlaceholders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new <code>PropertyPlaceholderHelper</code> 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
|
||||||
|
* (<code>true</code>) or cause an exception (<code>false</code>).
|
||||||
|
*/
|
||||||
|
public PropertyPlaceholder(String placeholderPrefix, String placeholderSuffix,
|
||||||
|
boolean ignoreUnresolvablePlaceholders) {
|
||||||
|
this.placeholderPrefix = placeholderPrefix;
|
||||||
|
this.placeholderSuffix = placeholderSuffix;
|
||||||
|
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces all placeholders of format <code>${name}</code> with the value returned from the supplied {@link
|
||||||
|
* PlaceholderResolver}.
|
||||||
|
*
|
||||||
|
* @param value the value containing the placeholders to be replaced.
|
||||||
|
* @param placeholderResolver the <code>PlaceholderResolver</code> to use for replacement.
|
||||||
|
* @return the supplied value with placeholders replaced inline.
|
||||||
|
*/
|
||||||
|
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
|
||||||
|
return parseStringValue(value, placeholderResolver, new HashSet<String>());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String parseStringValue(String strVal, PlaceholderResolver placeholderResolver,
|
||||||
|
Set<String> 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) {
|
||||||
|
// 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 (substringMatch(buf, index, this.placeholderSuffix)) {
|
||||||
|
if (withinNestedPlaceholder > 0) {
|
||||||
|
withinNestedPlaceholder--;
|
||||||
|
index = index + this.placeholderPrefix.length() - 1;
|
||||||
|
} else {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
} else if (substringMatch(buf, index, this.placeholderPrefix)) {
|
||||||
|
withinNestedPlaceholder++;
|
||||||
|
index = index + this.placeholderPrefix.length();
|
||||||
|
} else {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean substringMatch(CharSequence str, int index, CharSequence substring) {
|
||||||
|
for (int j = 0; j < substring.length(); j++) {
|
||||||
|
int i = index + j;
|
||||||
|
if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,11 +23,9 @@ import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ -687,26 +685,31 @@ public class Settings {
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
public Builder replacePropertyPlaceholders() {
|
public Builder replacePropertyPlaceholders() {
|
||||||
PropertyPlaceholder propertyPlaceholder = new PropertyPlaceholder("${", "}", false);
|
return replacePropertyPlaceholders(new PropertyPlaceholder("${", "}", false),
|
||||||
PropertyPlaceholder.PlaceholderResolver placeholderResolver = placeholderName -> {
|
placeholderName -> {
|
||||||
// system property
|
// system property
|
||||||
String value = System.getProperty(placeholderName);
|
String value = System.getProperty(placeholderName);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
// environment
|
// environment
|
||||||
value = System.getenv(placeholderName);
|
value = System.getenv(placeholderName);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
// current date
|
// current date
|
||||||
try {
|
try {
|
||||||
return DateTimeFormatter.ofPattern(placeholderName).format(LocalDate.now());
|
return DateTimeFormatter.ofPattern(placeholderName).format(LocalDate.now());
|
||||||
} catch (IllegalArgumentException | DateTimeException e) {
|
} catch (IllegalArgumentException | DateTimeException e) {
|
||||||
logger.log(Level.FINER, e.getMessage(), e);
|
logger.log(Level.FINER, e.getMessage(), e);
|
||||||
return map.get(placeholderName);
|
return map.get(placeholderName);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder replacePropertyPlaceholders(PropertyPlaceholder propertyPlaceholder,
|
||||||
|
PlaceholderResolver placeholderResolver) {
|
||||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||||
map.put(entry.getKey(), propertyPlaceholder.replacePlaceholders(entry.getValue(), placeholderResolver));
|
map.put(entry.getKey(), propertyPlaceholder.replacePlaceholders(entry.getValue(), placeholderResolver));
|
||||||
}
|
}
|
||||||
|
@ -717,133 +720,4 @@ public class Settings {
|
||||||
return new Settings(map);
|
return new Settings(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PropertyPlaceholder {
|
|
||||||
|
|
||||||
private final String placeholderPrefix;
|
|
||||||
|
|
||||||
private final String placeholderSuffix;
|
|
||||||
|
|
||||||
private final boolean ignoreUnresolvablePlaceholders;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new <code>PropertyPlaceholderHelper</code> 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
|
|
||||||
* (<code>true</code>) or cause an exception (<code>false</code>).
|
|
||||||
*/
|
|
||||||
public PropertyPlaceholder(String placeholderPrefix, String placeholderSuffix,
|
|
||||||
boolean ignoreUnresolvablePlaceholders) {
|
|
||||||
this.placeholderPrefix = placeholderPrefix;
|
|
||||||
this.placeholderSuffix = placeholderSuffix;
|
|
||||||
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces all placeholders of format <code>${name}</code> with the value returned from the supplied {@link
|
|
||||||
* PlaceholderResolver}.
|
|
||||||
*
|
|
||||||
* @param value the value containing the placeholders to be replaced.
|
|
||||||
* @param placeholderResolver the <code>PlaceholderResolver</code> to use for replacement.
|
|
||||||
* @return the supplied value with placeholders replaced inline.
|
|
||||||
*/
|
|
||||||
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
|
|
||||||
return parseStringValue(value, placeholderResolver, new HashSet<String>());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String parseStringValue(String strVal, PlaceholderResolver placeholderResolver,
|
|
||||||
Set<String> 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) {
|
|
||||||
// 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 (substringMatch(buf, index, this.placeholderSuffix)) {
|
|
||||||
if (withinNestedPlaceholder > 0) {
|
|
||||||
withinNestedPlaceholder--;
|
|
||||||
index = index + this.placeholderPrefix.length() - 1;
|
|
||||||
} else {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
} else if (substringMatch(buf, index, this.placeholderPrefix)) {
|
|
||||||
withinNestedPlaceholder++;
|
|
||||||
index = index + this.placeholderPrefix.length();
|
|
||||||
} else {
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean substringMatch(CharSequence str, int index, CharSequence substring) {
|
|
||||||
for (int j = 0; j < substring.length(); j++) {
|
|
||||||
int i = index + j;
|
|
||||||
if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Strategy interface used to resolve replacement values for placeholders contained in Strings.
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public 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 <code>null</code> if no replacement is to be made.
|
|
||||||
*/
|
|
||||||
String resolvePlaceholder(String placeholderName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue