Skip to content

Commit

Permalink
Handler in ConfigValue
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Nov 14, 2022
1 parent b60e9b1 commit 1fa5151
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 83 deletions.
20 changes: 20 additions & 0 deletions implementation/src/main/java/io/smallrye/config/ConfigValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class ConfigValue implements org.eclipse.microprofile.config.ConfigValue
private final int configSourcePosition;
private final int lineNumber;

private final String extendedExpressionHandler;
private final List<Problem> problems;

private ConfigValue(final ConfigValueBuilder builder) {
Expand All @@ -45,6 +46,7 @@ private ConfigValue(final ConfigValueBuilder builder) {
this.configSourceOrdinal = builder.configSourceOrdinal;
this.configSourcePosition = builder.configSourcePosition;
this.lineNumber = builder.lineNumber;
this.extendedExpressionHandler = builder.extendedExpressionHandler;
this.problems = builder.problems;
}

Expand Down Expand Up @@ -101,6 +103,10 @@ public String getLocation() {
return lineNumber != -1 ? configSourceName + ":" + lineNumber : configSourceName;
}

public String getExtendedExpressionHandler() {
return extendedExpressionHandler;
}

List<Problem> getProblems() {
return Collections.unmodifiableList(problems);
}
Expand Down Expand Up @@ -133,6 +139,10 @@ public ConfigValue withLineNumber(final int lineNumber) {
return from().withLineNumber(lineNumber).build();
}

public ConfigValue withExtendedExpressionHandler(final String extendedExpressionHandler) {
return from().withExtendedExpressionHandler(extendedExpressionHandler).build();
}

public ConfigValue noProblems() {
return from().noProblems().build();
}
Expand Down Expand Up @@ -192,6 +202,7 @@ ConfigValueBuilder from() {
.withConfigSourceOrdinal(configSourceOrdinal)
.withConfigSourcePosition(configSourcePosition)
.withLineNumber(lineNumber)
.withExtendedExpressionHandler(extendedExpressionHandler)
.withProblems(problems);
}

Expand All @@ -208,6 +219,7 @@ public static class ConfigValueBuilder {
private int configSourceOrdinal;
private int configSourcePosition;
private int lineNumber = -1;
private String extendedExpressionHandler;
private final List<Problem> problems = new ArrayList<>();

public ConfigValueBuilder withName(final String name) {
Expand Down Expand Up @@ -250,6 +262,11 @@ public ConfigValueBuilder withLineNumber(final int lineNumber) {
return this;
}

public ConfigValueBuilder withExtendedExpressionHandler(final String extendedExpressionHandler) {
this.extendedExpressionHandler = extendedExpressionHandler;
return this;
}

public ConfigValueBuilder noProblems() {
this.problems.clear();
return this;
Expand All @@ -266,6 +283,9 @@ public ConfigValueBuilder addProblem(final Problem problem) {
}

public ConfigValue build() {
if (!this.problems.isEmpty()) {
this.value = null;
}
return new ConfigValue(this);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package io.smallrye.config;

import static io.smallrye.common.expression.Expression.Flag.DOUBLE_COLON;
import static io.smallrye.common.expression.Expression.Flag.LENIENT_SYNTAX;
import static io.smallrye.common.expression.Expression.Flag.NO_SMART_BRACES;
import static io.smallrye.common.expression.Expression.Flag.NO_TRIM;
import static io.smallrye.config.ConfigMessages.msg;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;

import javax.annotation.Priority;

import org.eclipse.microprofile.config.Config;

import io.smallrye.common.expression.Expression;
import io.smallrye.common.expression.ResolveContext;
import io.smallrye.config.ConfigValidationException.Problem;
Expand All @@ -35,7 +32,7 @@ public ConfigValue getValue(final ConfigSourceInterceptorContext context, final
}

private ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name, final int depth) {
if (depth == MAX_DEPTH) {
if (depth >= MAX_DEPTH) {
throw msg.expressionExpansionTooDepth(name);
}

Expand All @@ -49,25 +46,39 @@ private ConfigValue getValue(final ConfigSourceInterceptorContext context, final
return null;
}

List<Problem> problems = new ArrayList<>();
ConfigValue.ConfigValueBuilder value = configValue.from();
Expression expression = Expression.compile(escapeDollarIfExists(configValue.getValue()), LENIENT_SYNTAX, NO_TRIM,
NO_SMART_BRACES);
NO_SMART_BRACES, DOUBLE_COLON);
String expanded = expression.evaluate(new BiConsumer<ResolveContext<RuntimeException>, StringBuilder>() {
@Override
public void accept(ResolveContext<RuntimeException> resolveContext, StringBuilder stringBuilder) {
ConfigValue resolve = getValue(context, resolveContext.getKey(), depth + 1);
String key = resolveContext.getKey();

// Requires a handler lookup
int index = key.indexOf("::");
if (index != -1) {
value.withExtendedExpressionHandler(key.substring(0, index));
stringBuilder.append(key, index + 2, key.length());
return;
}

// Expression lookup
ConfigValue resolve = getValue(context, key, depth + 1);
if (resolve != null) {
stringBuilder.append(resolve.getValue());
problems.addAll(resolve.getProblems());
if (resolve.getProblems().isEmpty()) {
stringBuilder.append(resolve.getValue());
} else {
value.withProblems(resolve.getProblems());
}
} else if (resolveContext.hasDefault()) {
resolveContext.expandDefault();
} else {
problems.add(new Problem(msg.expandingElementNotFound(resolveContext.getKey(), configValue.getName())));
value.addProblem(new Problem(msg.expandingElementNotFound(key, configValue.getName())));
}
}
});

return problems.isEmpty() ? configValue.withValue(expanded) : configValue.withValue(null).withProblems(problems);
return value.withValue(expanded).build();
}

/**
Expand Down
30 changes: 26 additions & 4 deletions implementation/src/main/java/io/smallrye/config/SecretKeys.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
package io.smallrye.config;

import java.io.Serializable;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Supplier;

@SuppressWarnings("squid:S5164")
public final class SecretKeys {
public final class SecretKeys implements Serializable {
private static final long serialVersionUID = -3226034787747746735L;

private static final ThreadLocal<Boolean> LOCKED = new ThreadLocal<>();

private SecretKeys() {
throw new UnsupportedOperationException();
private final Set<String> secrets;
private final Map<String, SecretKeysHandler> handlers;

SecretKeys(final Set<String> secrets, final Map<String, SecretKeysHandler> handlers) {
this.secrets = secrets;
this.handlers = handlers;
}

public String getSecretValue(final String handlerName, final String secretName) {
SecretKeysHandler handler = handlers.get(handlerName);
if (handler != null) {
return handler.decode(secretName);
}
throw new NoSuchElementException();
}

public boolean secretExistsWithName(final String secretName) {
return secrets.contains(secretName);
}

public static boolean isLocked() {
Boolean result = LOCKED.get();
return result == null ? true : result;
return result == null || result;
}

public static void doUnlocked(Runnable runnable) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,72 +1,27 @@
package io.smallrye.config;

import static io.smallrye.common.expression.Expression.Flag.DOUBLE_COLON;
import static io.smallrye.common.expression.Expression.Flag.LENIENT_SYNTAX;
import static io.smallrye.common.expression.Expression.Flag.NO_SMART_BRACES;
import static io.smallrye.common.expression.Expression.Flag.NO_TRIM;

import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;

import javax.annotation.Priority;

import io.smallrye.common.expression.Expression;
import io.smallrye.common.expression.ResolveContext;

@Priority(Priorities.LIBRARY + 100)
public class SecretKeysConfigSourceInterceptor implements ConfigSourceInterceptor {
private static final long serialVersionUID = 7291982039729980590L;

private final Set<String> secrets;
private final Map<String, SecretKeysHandler> handlers;

public SecretKeysConfigSourceInterceptor(final Set<String> secrets) {
this(secrets, Collections.emptyMap());
}
private final SecretKeys secrets;

public SecretKeysConfigSourceInterceptor(final Set<String> secrets, final Map<String, SecretKeysHandler> handlers) {
public SecretKeysConfigSourceInterceptor(final SecretKeys secrets) {
this.secrets = secrets;
this.handlers = handlers;
}

@Override
public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) {
if (SecretKeys.isLocked() && isSecret(name)) {
if (secrets.secretExistsWithName(name) && SecretKeys.isLocked()) {
throw ConfigMessages.msg.notAllowed(name);
}
ConfigValue configValue = context.proceed(name);

if (configValue == null || handlers.isEmpty()) {
return configValue;
}

// This executes before the Expression resolution.
// If we find a handler we execute it, if not we proceed
Expression expression = Expression.compile(configValue.getValue(), LENIENT_SYNTAX, NO_TRIM, NO_SMART_BRACES,
DOUBLE_COLON);
String secret = expression.evaluate(new BiConsumer<ResolveContext<RuntimeException>, StringBuilder>() {
@Override
public void accept(final ResolveContext<RuntimeException> context, final StringBuilder stringBuilder) {
String[] split = context.getKey().split("::");
if (split.length == 2) {
String key = split[0];
String secret = split[1];

SecretKeysHandler handler = handlers.get(key);
if (handler != null) {
stringBuilder.append(handler.handleSecret(secret));
}
}

// TODO - handle errors
}
});

return secret != null && !secret.isEmpty() ? configValue.withValue(secret) : configValue;
return context.proceed(name);
}

@Override
Expand All @@ -76,7 +31,7 @@ public Iterator<String> iterateNames(final ConfigSourceInterceptorContext contex
Iterator<String> namesIterator = context.iterateNames();
while (namesIterator.hasNext()) {
String name = namesIterator.next();
if (!secrets.contains(name)) {
if (!secrets.secretExistsWithName(name)) {
names.add(name);
}
}
Expand All @@ -92,16 +47,12 @@ public Iterator<ConfigValue> iterateValues(final ConfigSourceInterceptorContext
Iterator<ConfigValue> valuesIterator = context.iterateValues();
while (valuesIterator.hasNext()) {
ConfigValue value = valuesIterator.next();
if (!secrets.contains(value.getName())) {
if (!secrets.secretExistsWithName(value.getName())) {
values.add(value);
}
}
return values.iterator();
}
return context.iterateValues();
}

private boolean isSecret(final String name) {
return secrets.contains(name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

@Experimental("")
public interface SecretKeysHandler {
String handleSecret(String secret);
String decode(String secret);

String getName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.smallrye.config;

public class SecretKeysHandlerConfigSourceInterceptor implements ConfigSourceInterceptor {
private static final long serialVersionUID = -5228028387733656005L;

private final SecretKeys secretKeys;

public SecretKeysHandlerConfigSourceInterceptor(final SecretKeys secretKeys) {
this.secretKeys = secretKeys;
}

@Override
public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) {
ConfigValue configValue = context.proceed(name);
if (configValue != null && configValue.getValue() != null) {
String handler = configValue.getExtendedExpressionHandler();
if (handler != null) {
return configValue.withValue(secretKeys.getSecretValue(handler, configValue.getValue()));
}
}
return configValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorConte
boolean expressions = true;
ConfigValue expressionsValue = context.proceed(Config.PROPERTY_EXPRESSIONS_ENABLED);
if (expressionsValue != null) {
expressions = Boolean.valueOf(expressionsValue.getValue());
expressions = Boolean.parseBoolean(expressionsValue.getValue());
}
return new ExpressionConfigSourceInterceptor(expressions);
}
Expand All @@ -293,22 +293,37 @@ public OptionalInt getPriority() {
return OptionalInt.of(Priorities.LIBRARY + 300);
}
}));

Map<String, SecretKeysHandler> discoveredHandlers = new HashMap<>();
ServiceLoader<SecretKeysHandler> secretKeysHandlers = ServiceLoader.load(SecretKeysHandler.class, classLoader);
for (SecretKeysHandler secretKeysHandler : secretKeysHandlers) {
discoveredHandlers.put(secretKeysHandler.getName(), secretKeysHandler);
}

SecretKeys secretKeys = new SecretKeys(this.secretKeys, discoveredHandlers);

interceptors.add(new InterceptorWithPriority(new ConfigSourceInterceptorFactory() {
@Override
public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) {
Map<String, SecretKeysHandler> discoveredHandlers = new HashMap<>();
ServiceLoader<SecretKeysHandler> secretKeysHandlers = ServiceLoader.load(SecretKeysHandler.class, classLoader);
for (SecretKeysHandler secretKeysHandler : secretKeysHandlers) {
discoveredHandlers.put(secretKeysHandler.getName(), secretKeysHandler);
}
return new SecretKeysConfigSourceInterceptor(secretKeys, discoveredHandlers);
return new SecretKeysConfigSourceInterceptor(secretKeys);
}

@Override
public OptionalInt getPriority() {
return OptionalInt.of(Priorities.LIBRARY + 100);
}
}));
interceptors.add(new InterceptorWithPriority(new ConfigSourceInterceptorFactory() {
@Override
public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) {
return new SecretKeysHandlerConfigSourceInterceptor(secretKeys);
}

@Override
public OptionalInt getPriority() {
return OptionalInt.of(Priorities.LIBRARY + 310);
}
}));

return interceptors;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ void mapping() {

private static Config buildConfig(String... keyValues) {
return new SmallRyeConfigBuilder()
.addDefaultSources()
.addDefaultInterceptors()
.withSources(KeyValuesConfigSource.config(keyValues))
.withSecretKeys("secret")
Expand Down
Loading

0 comments on commit 1fa5151

Please sign in to comment.