Skip to content

Commit

Permalink
Refactor EndpointRuleSetExtension SPI loading
Browse files Browse the repository at this point in the history
Updates the loading of the EndpointRuleSetExtension SPI to use a lazy
instance holder that is loaded when referenced instead of a lock-based
static initializer. The APIs that access the holder are labeled internal,
as the access patterns may change if the content needs to vary by
instance of the EndpointRuleSet.
  • Loading branch information
kstich committed Sep 19, 2023
1 parent 23bd4a4 commit b742e4b
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.rulesengine.language;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Function;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionDefinition;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionNode;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.LibraryFunction;
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
import software.amazon.smithy.rulesengine.validators.AuthSchemeValidator;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* Provides access to endpoint components loaded through {@link EndpointRuleSetExtension}s.
*/
@SmithyInternalApi
public interface EndpointComponentFactory {

/**
* Returns true if a built-in of the provided name has been registered.
*
* @param name the name of the built-in to check for.
* @return true if the built-in is present, false otherwise.
*/
boolean hasBuiltIn(String name);

/**
* Gets the built-in names as a joined string.
*
* @return a string of the built-in names.
*/
String getKeyString();

/**
* Creates a {@link LibraryFunction} factory function using the loaded function definitions.
*
* @return the created factory.
*/
Function<FunctionNode, Optional<LibraryFunction>> createFunctionFactory();

/**
* Gets loaded authentication scheme validators.
*
* @return a list of {@link AuthSchemeValidator}s.
*/
List<AuthSchemeValidator> getAuthSchemeValidators();

static EndpointComponentFactory createServiceFactory(
Map<String, Parameter> builtIns,
Map<String, FunctionDefinition> libraryFunctions,
List<AuthSchemeValidator> authSchemeValidators
) {
return new EndpointComponentFactory() {
@Override
public boolean hasBuiltIn(String name) {
return builtIns.containsKey(name);
}

@Override
public String getKeyString() {
return String.join(", ", builtIns.keySet());
}

@Override
public Function<FunctionNode, Optional<LibraryFunction>> createFunctionFactory() {
return node -> {
if (libraryFunctions.containsKey(node.getName())) {
return Optional.of(libraryFunctions.get(node.getName()).createFunction(node));
}
return Optional.empty();
};
}

@Override
public List<AuthSchemeValidator> getAuthSchemeValidators() {
return authSchemeValidators;
}
};
}

static EndpointComponentFactory createServiceFactory(ClassLoader classLoader) {
Map<String, Parameter> builtIns = new HashMap<>();
Map<String, FunctionDefinition> libraryFunctions = new HashMap<>();
List<AuthSchemeValidator> authSchemeValidators = new ArrayList<>();
for (EndpointRuleSetExtension extension : ServiceLoader.load(EndpointRuleSetExtension.class, classLoader)) {
String name;
for (Parameter builtIn : extension.getBuiltIns()) {
name = builtIn.getBuiltIn().get();
if (builtIns.containsKey(name)) {
throw new RuntimeException("Attempted to load a duplicate built-in parameter: " + name);
}
builtIns.put(name, builtIn);
}

for (FunctionDefinition functionDefinition : extension.getLibraryFunctions()) {
name = functionDefinition.getId();
if (libraryFunctions.containsKey(name)) {
throw new RuntimeException("Attempted to load a duplicate library function: " + name);
}
libraryFunctions.put(name, functionDefinition);
}

authSchemeValidators.addAll(extension.getAuthSchemeValidators());
}

return createServiceFactory(builtIns, libraryFunctions, authSchemeValidators);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@

import static software.amazon.smithy.rulesengine.language.error.RuleError.context;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Function;
import software.amazon.smithy.model.FromSourceLocation;
import software.amazon.smithy.model.SourceLocation;
Expand All @@ -26,16 +22,15 @@
import software.amazon.smithy.rulesengine.language.evaluation.Scope;
import software.amazon.smithy.rulesengine.language.evaluation.TypeCheck;
import software.amazon.smithy.rulesengine.language.evaluation.type.Type;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionDefinition;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.FunctionNode;
import software.amazon.smithy.rulesengine.language.syntax.expressions.functions.LibraryFunction;
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters;
import software.amazon.smithy.rulesengine.language.syntax.rule.EndpointRule;
import software.amazon.smithy.rulesengine.language.syntax.rule.Rule;
import software.amazon.smithy.rulesengine.validators.AuthSchemeValidator;
import software.amazon.smithy.utils.BuilderRef;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.SmithyInternalApi;
import software.amazon.smithy.utils.SmithyUnstableApi;
import software.amazon.smithy.utils.StringUtils;
import software.amazon.smithy.utils.ToSmithyBuilder;
Expand All @@ -50,10 +45,10 @@ public final class EndpointRuleSet implements FromSourceLocation, ToNode, ToSmit
private static final String PARAMETERS = "parameters";
private static final String RULES = "rules";

private static boolean loaded = false;
private static final Map<String, Parameter> BUILT_INS = new HashMap<>();
private static final Map<String, FunctionDefinition> FUNCTIONS = new HashMap<>();
private static final List<AuthSchemeValidator> AUTH_SCHEME_VALIDATORS = new ArrayList<>();
private static final class LazyEndpointComponentFactoryHolder {
static final EndpointComponentFactory INSTANCE = EndpointComponentFactory.createServiceFactory(
EndpointRuleSet.class.getClassLoader());
}

private final Parameters parameters;
private final List<Rule> rules;
Expand Down Expand Up @@ -84,7 +79,6 @@ public static Builder builder() {
* @return the created EndpointRuleSet.
*/
public static EndpointRuleSet fromNode(Node node) throws RuleError {
loadExtensions();
return RuleError.context("when parsing endpoint ruleset", () -> {
ObjectNode objectNode = node.expectObjectNode("The root of a ruleset must be an object");

Expand Down Expand Up @@ -195,75 +189,45 @@ public String toString() {
return builder.toString();
}

private static void loadExtensions() {
if (loaded) {
return;
}
loaded = true;

for (EndpointRuleSetExtension extension : ServiceLoader.load(EndpointRuleSetExtension.class)) {
String name;
for (Parameter builtIn : extension.getBuiltIns()) {
name = builtIn.getBuiltIn().get();
if (BUILT_INS.containsKey(name)) {
throw new RuntimeException("Attempted to load a duplicate built-in parameter: " + name);
}
BUILT_INS.put(name, builtIn);
}

for (FunctionDefinition functionDefinition : extension.getLibraryFunctions()) {
name = functionDefinition.getId();
if (FUNCTIONS.containsKey(name)) {
throw new RuntimeException("Attempted to load a duplicate library function: " + name);
}
FUNCTIONS.put(name, functionDefinition);
}

AUTH_SCHEME_VALIDATORS.addAll(extension.getAuthSchemeValidators());
}
}


/**
* Returns true if a built-in of the provided name has been registered.
*
* @param name the name of the built-in to check for.
* @return true if the built-in is present, false otherwise.
*/
@SmithyInternalApi
public static boolean hasBuiltIn(String name) {
return BUILT_INS.containsKey(name);
return LazyEndpointComponentFactoryHolder.INSTANCE.hasBuiltIn(name);
}

/**
* Gets the built-in names as a joined string.
*
* @return a string of the built-in names.
*/
@SmithyInternalApi
public static String getKeyString() {
return String.join(", ", BUILT_INS.keySet());
return LazyEndpointComponentFactoryHolder.INSTANCE.getKeyString();
}

/**
* Creates a {@link LibraryFunction} factory function using the loaded function definitions.
*
* @return the created factory.
*/
@SmithyInternalApi
public static Function<FunctionNode, Optional<LibraryFunction>> createFunctionFactory() {
return node -> {
if (FUNCTIONS.containsKey(node.getName())) {
return Optional.of(FUNCTIONS.get(node.getName()).createFunction(node));
}
return Optional.empty();
};
return LazyEndpointComponentFactoryHolder.INSTANCE.createFunctionFactory();
}

/**
* Gets loaded authentication scheme validators.
*
* @return a list of {@link AuthSchemeValidator}s.
*/
@SmithyInternalApi
public static List<AuthSchemeValidator> getAuthSchemeValidators() {
return AUTH_SCHEME_VALIDATORS;
return LazyEndpointComponentFactoryHolder.INSTANCE.getAuthSchemeValidators();
}

/**
Expand Down

0 comments on commit b742e4b

Please sign in to comment.