Skip to content

Commit

Permalink
Adds support for complex Configurable factory methods.
Browse files Browse the repository at this point in the history
Adds @configurable to signify that this is a complex config object and use boot binding to create and populate it.

Retry and CircuitBreaker are initially supported.

Adds SimpleFilterSupplier to ease creation of FilterSupplier objects.

Fixes gh-3172
  • Loading branch information
spencergibb committed Dec 20, 2023
1 parent 88662b6 commit e360c22
Show file tree
Hide file tree
Showing 16 changed files with 188 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ public <T extends Supplier<Collection<Method>>, R> void doDiscover(Class<T> supp
}
catch (NoClassDefFoundError e) {
if (log.isDebugEnabled()) {
log.debug(LogMessage.format("NoClassDefFoundError discovering supplier %s for type %s", supplierClass, returnType));
} else if (log.isTraceEnabled()) {
log.debug(LogMessage.format("NoClassDefFoundError discovering supplier %s for type %s", supplierClass, returnType), e);
log.debug(LogMessage.format("NoClassDefFoundError discovering supplier %s for type %s",
supplierClass, returnType));
}
else if (log.isTraceEnabled()) {
log.debug(LogMessage.format("NoClassDefFoundError discovering supplier %s for type %s",
supplierClass, returnType), e);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2013-2023 the original author or 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 org.springframework.cloud.gateway.server.mvc.common;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Indexed;

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Configurable {

Class<?> value() default Void.class;

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
Expand All @@ -32,20 +33,27 @@
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.handler.IgnoreTopLevelConverterNotFoundBindHandler;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.cloud.gateway.server.mvc.common.Configurable;
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
import org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions;
import org.springframework.cloud.gateway.server.mvc.filter.FilterDiscoverer;
import org.springframework.cloud.gateway.server.mvc.handler.HandlerDiscoverer;
import org.springframework.cloud.gateway.server.mvc.invoke.InvocationContext;
import org.springframework.cloud.gateway.server.mvc.invoke.OperationArgumentResolver;
import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter;
import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameters;
import org.springframework.cloud.gateway.server.mvc.invoke.ParameterValueMapper;
import org.springframework.cloud.gateway.server.mvc.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod;
import org.springframework.cloud.gateway.server.mvc.invoke.reflect.ReflectiveOperationInvoker;
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.log.LogMessage;
import org.springframework.core.type.AnnotationMetadata;
Expand Down Expand Up @@ -264,6 +272,10 @@ private Optional<NormalizedOperationMethod> findOperation(MultiValueMap<String,
private static boolean matchOperation(NormalizedOperationMethod operationMethod, Map<String, String> args) {
Map<String, String> normalizedArgs = operationMethod.getNormalizedArgs();
OperationParameters parameters = operationMethod.getParameters();
if (operationMethod.isConfigurable()) {
// this is a special case
return true;
}
if (parameters.getParameterCount() != normalizedArgs.size()) {
return false;
}
Expand All @@ -277,13 +289,37 @@ private static boolean matchOperation(NormalizedOperationMethod operationMethod,
}

private <T> T invokeOperation(OperationMethod operationMethod, Map<String, String> operationArgs) {
Map<String, Object> args = new HashMap<>(operationArgs);
Map<String, Object> args = new HashMap<>();
if (operationMethod.isConfigurable()) {
OperationParameter operationParameter = operationMethod.getParameters().get(0);
Object config = bindConfigurable(operationMethod, args, operationParameter);
args.put(operationParameter.getName(), config);
}
else {
args.putAll(operationArgs);
}
ReflectiveOperationInvoker operationInvoker = new ReflectiveOperationInvoker(operationMethod,
this.parameterValueMapper);
InvocationContext context = new InvocationContext(args, trueNullOperationArgumentResolver);
return operationInvoker.invoke(context);
}

private static Object bindConfigurable(OperationMethod operationMethod, Map<String, Object> args,
OperationParameter operationParameter) {
Class<?> configurableType = operationParameter.getType();
Configurable configurable = operationMethod.getMethod().getAnnotation(Configurable.class);
if (configurable != null && !configurable.value().equals(Void.class)) {
configurableType = configurable.value();
}
Bindable<?> bindable = Bindable.of(configurableType);
List<ConfigurationPropertySource> propertySources = Collections
.singletonList(new MapConfigurationPropertySource(args));
// TODO: potentially deal with conversion service
Binder binder = new Binder(propertySources, null, DefaultConversionService.getSharedInstance());
Object config = binder.bindOrCreate("", bindable, new IgnoreTopLevelConverterNotFoundBindHandler());
return config;
}

static class TrueNullOperationArgumentResolver implements OperationArgumentResolver {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.List;
import java.util.Map;

import org.springframework.cloud.gateway.server.mvc.common.Configurable;
import org.springframework.cloud.gateway.server.mvc.common.NameUtils;
import org.springframework.cloud.gateway.server.mvc.common.Shortcut;
import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter;
Expand Down Expand Up @@ -50,6 +51,11 @@ public Method getMethod() {
return delegate.getMethod();
}

public boolean isConfigurable() {
Configurable annotation = delegate.getMethod().getAnnotation(Configurable.class);
return annotation != null && delegate.getParameters().getParameterCount() == 1;
}

@Override
public OperationParameters getParameters() {
return delegate.getParameters();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

package org.springframework.cloud.gateway.server.mvc.filter;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
Expand Down Expand Up @@ -189,11 +186,10 @@ public RateLimitConfig setHeaderName(String headerName) {

}

static class FilterSupplier implements org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier {
public static class FilterSupplier extends SimpleFilterSupplier {

@Override
public Collection<Method> get() {
return Arrays.asList(Bucket4jFilterFunctions.class.getMethods());
public FilterSupplier() {
super(Bucket4jFilterFunctions.class);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@
package org.springframework.cloud.gateway.server.mvc.filter;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
Expand All @@ -32,6 +30,7 @@

import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.gateway.server.mvc.common.Configurable;
import org.springframework.cloud.gateway.server.mvc.common.HttpStatusHolder;
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
import org.springframework.cloud.gateway.server.mvc.common.Shortcut;
Expand Down Expand Up @@ -66,6 +65,12 @@ public static HandlerFilterFunction<ServerResponse, ServerResponse> circuitBreak
Consumer<CircuitBreakerConfig> configConsumer) {
CircuitBreakerConfig config = new CircuitBreakerConfig();
configConsumer.accept(config);
return circuitBreaker(config);
}

@Shortcut
@Configurable
public static HandlerFilterFunction<ServerResponse, ServerResponse> circuitBreaker(CircuitBreakerConfig config) {
Set<HttpStatusCode> failureStatuses = config.getStatusCodes().stream()
.map(status -> HttpStatusHolder.valueOf(status).resolve()).collect(Collectors.toSet());
return (request, next) -> {
Expand Down Expand Up @@ -191,11 +196,10 @@ public CircuitBreakerStatusCodeException(HttpStatusCode statusCode) {

}

public static class FilterSupplier implements org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier {
public static class FilterSupplier extends SimpleFilterSupplier {

@Override
public Collection<Method> get() {
return Arrays.asList(CircuitBreakerFilterFunctions.class.getMethods());
public FilterSupplier() {
super(CircuitBreakerFilterFunctions.class);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

package org.springframework.cloud.gateway.server.mvc.filter;

import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Consumer;

import org.springframework.cloud.gateway.server.mvc.common.HttpStatusHolder;
Expand Down Expand Up @@ -228,11 +225,10 @@ static HandlerFilterFunction<ServerResponse, ServerResponse> setStatus(HttpStatu
return ofResponseProcessor(AfterFilterFunctions.setStatus(statusCode));
}

class FilterSupplier implements org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier {
class FilterSupplier extends SimpleFilterSupplier {

@Override
public Collection<Method> get() {
return Arrays.asList(FilterFunctions.class.getMethods());
public FilterSupplier() {
super(FilterFunctions.class);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;

import org.springframework.cloud.gateway.server.mvc.common.Configurable;
import org.springframework.cloud.gateway.server.mvc.common.Shortcut;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -53,6 +55,12 @@ public static HandlerFilterFunction<ServerResponse, ServerResponse> retry(int re
public static HandlerFilterFunction<ServerResponse, ServerResponse> retry(Consumer<RetryConfig> configConsumer) {
RetryConfig config = new RetryConfig();
configConsumer.accept(config);
return retry(config);
}

@Shortcut
@Configurable
public static HandlerFilterFunction<ServerResponse, ServerResponse> retry(RetryConfig config) {
RetryTemplateBuilder retryTemplateBuilder = RetryTemplate.builder();
CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy();
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
Expand Down Expand Up @@ -191,4 +199,12 @@ public ServerResponse getResponse() {

}

public static class FilterSupplier extends SimpleFilterSupplier {

public FilterSupplier() {
super(RetryFilterFunctions.class);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2013-2023 the original author or 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 org.springframework.cloud.gateway.server.mvc.filter;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;

public class SimpleFilterSupplier implements FilterSupplier {

private final Class<?> filtersClass;

public SimpleFilterSupplier(Class<?> filtersClass) {
this.filtersClass = filtersClass;
}

@Override
public Collection<Method> get() {
return Arrays.asList(filtersClass.getMethods());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

package org.springframework.cloud.gateway.server.mvc.filter;

import java.lang.reflect.Method;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collection;

import org.springframework.cloud.gateway.server.mvc.common.Shortcut;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
Expand Down Expand Up @@ -58,11 +55,10 @@ public static HandlerFilterFunction<ServerResponse, ServerResponse> tokenRelay()
};
}

class FilterSupplier implements org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier {
public static class FilterSupplier extends SimpleFilterSupplier {

@Override
public Collection<Method> get() {
return Arrays.asList(TokenRelayFilterFunctions.class.getMethods());
public FilterSupplier() {
super(TokenRelayFilterFunctions.class);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public static HandlerFunction<ServerResponse> http() {
return new LookupProxyExchangeHandlerFunction();
}

public static HandlerFunction<ServerResponse> no() {
return http();
}

static class LookupProxyExchangeHandlerFunction implements HandlerFunction<ServerResponse> {

private final URI uri;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ public interface OperationMethod {

OperationParameters getParameters();

default boolean isConfigurable() {
return false;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\
org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.FilterSupplier,\
org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.FilterSupplier,\
org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions.FilterSupplier,\
org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions.FilterSupplier,\
org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.FilterSupplier

Expand Down
Loading

0 comments on commit e360c22

Please sign in to comment.