Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.micronaut.web.router.DefaultRouteBuilder Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original 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 io.micronaut.web.router;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.BeanLocator;
import io.micronaut.context.ExecutionHandleLocator;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ObjectUtils;
import io.micronaut.http.HttpMethod;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.RouteCondition;
import io.micronaut.http.body.MessageBodyHandlerRegistry;
import io.micronaut.http.filter.FilterOrder;
import io.micronaut.http.filter.GenericHttpFilter;
import io.micronaut.http.filter.HttpFilter;
import io.micronaut.http.uri.UriMatchTemplate;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.MethodExecutionHandle;
import io.micronaut.inject.MethodReference;
import io.micronaut.inject.annotation.EvaluatedAnnotationValue;
import io.micronaut.scheduling.executor.ExecutorSelector;
import io.micronaut.scheduling.executor.ThreadSelection;
import io.micronaut.web.router.exceptions.RoutingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* A DefaultRouteBuilder implementation for building roots.
*
* @author Graeme Rocher
* @since 1.0
*/
public abstract class DefaultRouteBuilder implements RouteBuilder {
/**
* A {@link io.micronaut.web.router.RouteBuilder.UriNamingStrategy} whereby camel case conventions are used.
*/
public static final UriNamingStrategy CAMEL_CASE_NAMING_STRATEGY = new UriNamingStrategy() {
};
protected static final Logger LOG = LoggerFactory.getLogger(DefaultRouteBuilder.class);
protected final ExecutionHandleLocator executionHandleLocator;
protected final UriNamingStrategy uriNamingStrategy;
protected final ConversionService conversionService;
protected final Charset defaultCharset;
private final ExecutorSelector executorSelector;
private final MessageBodyHandlerRegistry messageBodyHandlerRegistry;
private DefaultUriRoute currentParentRoute;
private final List uriRoutes = new ArrayList<>();
private final List statusRoutes = new ArrayList<>();
private final List errorRoutes = new ArrayList<>();
private final List filterRoutes = new ArrayList<>();
private final Set exposedPorts = new HashSet<>(5);
/**
* @param executionHandleLocator The execution handler locator
*/
public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator) {
this(executionHandleLocator, CAMEL_CASE_NAMING_STRATEGY);
}
/**
* @param executionHandleLocator The execution handler locator
* @param uriNamingStrategy The URI naming strategy
*/
public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy) {
this(executionHandleLocator, uriNamingStrategy, ConversionService.SHARED);
}
/**
* @param executionHandleLocator The execution handler locator
* @param uriNamingStrategy The URI naming strategy
* @param conversionService The conversion service
*/
public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService conversionService) {
this.executionHandleLocator = executionHandleLocator;
this.uriNamingStrategy = uriNamingStrategy;
this.conversionService = conversionService;
if (executionHandleLocator instanceof ApplicationContext applicationContext) {
Environment environment = applicationContext.getEnvironment();
defaultCharset = environment.get("micronaut.application.default-charset", Charset.class, StandardCharsets.UTF_8);
this.executorSelector = applicationContext.findBean(ExecutorSelector.class).orElse(null);
this.messageBodyHandlerRegistry = applicationContext.findBean(MessageBodyHandlerRegistry.class).orElse(MessageBodyHandlerRegistry.EMPTY);
} else {
defaultCharset = StandardCharsets.UTF_8;
this.executorSelector = null;
this.messageBodyHandlerRegistry = MessageBodyHandlerRegistry.EMPTY;
}
}
@Override
public Set getExposedPorts() {
return exposedPorts;
}
@Override
public List getFilterRoutes() {
return filterRoutes;
}
@Override
public FilterRoute addFilter(String pathPattern, BeanLocator beanLocator, BeanDefinition extends HttpFilter> definition) {
FilterRoute fr = new DefaultFilterRoute(
pathPattern,
() -> GenericHttpFilter.createLegacyFilter(beanLocator.getBean(definition), new FilterOrder.Dynamic(definition.getOrder())),
definition,
false);
filterRoutes.add(fr);
return fr;
}
final FilterRoute addFilter(Supplier internalFilter, AnnotationMetadata annotationMetadata, boolean isPreMatching) {
FilterRoute fr = new DefaultFilterRoute(internalFilter, annotationMetadata, isPreMatching);
filterRoutes.add(fr);
return fr;
}
@Override
public List getStatusRoutes() {
return Collections.unmodifiableList(statusRoutes);
}
@Override
public List getErrorRoutes() {
return Collections.unmodifiableList(errorRoutes);
}
@Override
public List getUriRoutes() {
return Collections.unmodifiableList(uriRoutes);
}
@Override
public UriNamingStrategy getUriNamingStrategy() {
return uriNamingStrategy;
}
@Override
public ResourceRoute resources(Class> cls) {
return new DefaultResourceRoute(cls);
}
@Override
public ResourceRoute single(Class> cls) {
return new DefaultSingleRoute(cls);
}
@Override
public StatusRoute status(Class> originatingClass, HttpStatus status, Class> type, String method, Class>[] parameterTypes) {
Optional> executionHandle = executionHandleLocator.findExecutionHandle((Class) type, method, parameterTypes);
MethodExecutionHandle executableHandle = executionHandle.orElseThrow(() ->
new RoutingException("No such route: " + type.getName() + "." + method)
);
DefaultStatusRoute statusRoute = new DefaultStatusRoute(originatingClass, status, executableHandle, conversionService);
this.statusRoutes.add(statusRoute);
return statusRoute;
}
@Override
public StatusRoute status(HttpStatus status, Class> type, String method, Class>[] parameterTypes) {
Optional> executionHandle = executionHandleLocator.findExecutionHandle((Class) type, method, parameterTypes);
MethodExecutionHandle executableHandle = executionHandle.orElseThrow(() ->
new RoutingException("No such route: " + type.getName() + "." + method)
);
DefaultStatusRoute statusRoute = new DefaultStatusRoute(status, executableHandle, conversionService);
this.statusRoutes.add(statusRoute);
return statusRoute;
}
@Override
public ErrorRoute error(Class> originatingClass, Class extends Throwable> error, Class> type, String method, Class>[] parameterTypes) {
Optional> executionHandle = executionHandleLocator.findExecutionHandle((Class) type, method, parameterTypes);
MethodExecutionHandle executableHandle = executionHandle.orElseThrow(() ->
new RoutingException("No such route: " + type.getName() + "." + method)
);
DefaultErrorRoute errorRoute = new DefaultErrorRoute(originatingClass, error, executableHandle, conversionService);
this.errorRoutes.add(errorRoute);
return errorRoute;
}
@Override
public ErrorRoute error(Class extends Throwable> error, Class> type, String method, Class>[] parameterTypes) {
Optional> executionHandle = executionHandleLocator.findExecutionHandle((Class) type, method, parameterTypes);
MethodExecutionHandle executableHandle = executionHandle.orElseThrow(() ->
new RoutingException("No such route: " + type.getName() + "." + method)
);
DefaultErrorRoute errorRoute = new DefaultErrorRoute(error, executableHandle, conversionService);
this.errorRoutes.add(errorRoute);
return errorRoute;
}
@Override
public UriRoute GET(String uri, Object target, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.GET, uri, target.getClass(), method, parameterTypes);
}
@Override
public UriRoute GET(String uri, Class> type, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.GET, uri, type, method, parameterTypes);
}
@Override
public UriRoute POST(String uri, Object target, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.POST, uri, target.getClass(), method, parameterTypes);
}
@Override
public UriRoute POST(String uri, Class> type, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.POST, uri, type, method, parameterTypes);
}
@Override
public UriRoute PUT(String uri, Object target, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.PUT, uri, target.getClass(), method, parameterTypes);
}
@Override
public UriRoute PUT(String uri, Class> type, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.PUT, uri, type, method, parameterTypes);
}
@Override
public UriRoute PATCH(String uri, Object target, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.PATCH, uri, target.getClass(), method, parameterTypes);
}
@Override
public UriRoute PATCH(String uri, Class> type, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.PATCH, uri, type, method, parameterTypes);
}
@Override
public UriRoute DELETE(String uri, Object target, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.DELETE, uri, target.getClass(), method, parameterTypes);
}
@Override
public UriRoute DELETE(String uri, Class> type, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.DELETE, uri, type, method, parameterTypes);
}
@Override
public UriRoute OPTIONS(String uri, Object target, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.OPTIONS, uri, target.getClass(), method, parameterTypes);
}
@Override
public UriRoute OPTIONS(String uri, Class> type, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.OPTIONS, uri, type, method, parameterTypes);
}
@Override
public UriRoute HEAD(String uri, Object target, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.HEAD, uri, target.getClass(), method, parameterTypes);
}
@Override
public UriRoute HEAD(String uri, Class> type, String method, Class>... parameterTypes) {
return buildRoute(HttpMethod.HEAD, uri, type, method, parameterTypes);
}
@Override
public UriRoute TRACE(String uri, Object target, String method, Class>[] parameterTypes) {
return buildRoute(HttpMethod.TRACE, uri, target.getClass(), method, parameterTypes);
}
@Override
public UriRoute TRACE(String uri, Class> type, String method, Class>[] parameterTypes) {
return buildRoute(HttpMethod.TRACE, uri, type, method, parameterTypes);
}
@Override
public UriRoute GET(String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(HttpMethod.GET, uri, beanDefinition, method);
}
@Override
public UriRoute POST(String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(HttpMethod.POST, uri, beanDefinition, method);
}
@Override
public UriRoute PUT(String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(HttpMethod.PUT, uri, beanDefinition, method);
}
@Override
public UriRoute PATCH(String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(HttpMethod.PATCH, uri, beanDefinition, method);
}
@Override
public UriRoute DELETE(String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(HttpMethod.DELETE, uri, beanDefinition, method);
}
@Override
public UriRoute OPTIONS(String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(HttpMethod.OPTIONS, uri, beanDefinition, method);
}
@Override
public UriRoute HEAD(String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(HttpMethod.HEAD, uri, beanDefinition, method);
}
@Override
public UriRoute TRACE(String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(HttpMethod.TRACE, uri, beanDefinition, method);
}
/**
* Build a route.
*
* @param httpMethod The HTTP method
* @param uri The URI
* @param type The type
* @param method The method
* @param parameterTypes Parameters
*
* @return an {@link UriRoute}
*/
protected UriRoute buildRoute(HttpMethod httpMethod, String uri, Class> type, String method, Class>... parameterTypes) {
Optional extends MethodExecutionHandle> executionHandle =
executionHandleLocator.findExecutionHandle((Class) type, method, parameterTypes);
MethodExecutionHandle executableHandle = executionHandle.orElseThrow(() ->
new RoutingException("No such route: " + type.getName() + "." + method)
);
return buildRoute(httpMethod, uri, executableHandle);
}
/**
* Build a route.
*
* @param httpMethod The HTTP method
* @param uri The URI
* @param executableHandle The executable handle
*
* @return an {@link UriRoute}
*/
protected UriRoute buildRoute(HttpMethod httpMethod, String uri, MethodExecutionHandle executableHandle) {
return buildRoute(httpMethod.name(), httpMethod, uri, executableHandle);
}
/**
* Build a route.
*
* @param httpMethod The HTTP method
* @param uri The URI
* @param mediaTypes The media types
* @param executableHandle The executable handle
*
* @since 4.2.0
* @return an {@link UriRoute}
*/
protected UriRoute buildRoute(HttpMethod httpMethod, String uri, List mediaTypes, MethodExecutionHandle executableHandle) {
return buildRoute(httpMethod.name(), httpMethod, uri, mediaTypes, executableHandle);
}
private UriRoute buildRoute(String httpMethodName, HttpMethod httpMethod, String uri, MethodExecutionHandle executableHandle) {
return buildRoute(httpMethodName, httpMethod, uri, List.of(MediaType.APPLICATION_JSON_TYPE), executableHandle);
}
private UriRoute buildRoute(String httpMethodName, HttpMethod httpMethod, String uri, List mediaTypes, MethodExecutionHandle executableHandle) {
DefaultUriRoute route;
if (currentParentRoute != null) {
route = new DefaultUriRoute(
httpMethod,
currentParentRoute.uriMatchTemplate.nest(uri),
mediaTypes,
executableHandle,
httpMethodName,
conversionService
);
currentParentRoute.nestedRoutes.add(route);
} else {
route = new DefaultUriRoute(httpMethod, uri, mediaTypes, executableHandle, httpMethodName, conversionService);
}
this.uriRoutes.add(route);
return route;
}
private UriRoute buildBeanRoute(HttpMethod httpMethod, String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
return buildBeanRoute(httpMethod.name(), httpMethod, uri, beanDefinition, method);
}
/**
* A special case that is required for non-standard http methods.
* @param httpMethodName The name of method. For standard http methods matches {@link HttpMethod#name()}
* @param httpMethod The http method. Is {@link HttpMethod#CUSTOM} for non-standard http methods.
* @param uri The uri.
* @param beanDefinition The definition of the bean.
* @param method The method description
* @return The uri route corresponding to the method.
*/
protected UriRoute buildBeanRoute(String httpMethodName, HttpMethod httpMethod, String uri, BeanDefinition> beanDefinition, ExecutableMethod, ?> method) {
MethodExecutionHandle executionHandle = (MethodExecutionHandle) executionHandleLocator
.createExecutionHandle(beanDefinition, (ExecutableMethod) method);
return buildRoute(httpMethodName, httpMethod, uri, executionHandle);
}
/**
* Abstract class for base {@link MethodBasedRouteInfo}.
*/
abstract static class AbstractRoute implements Route {
protected final List>> conditions = new ArrayList<>();
protected final MethodExecutionHandle targetMethod;
protected final ConversionService conversionService;
protected List consumesMediaTypes;
protected List producesMediaTypes = List.of();
protected String bodyArgumentName;
protected Argument> bodyArgument;
/**
* @param targetMethod The target method execution handle
* @param conversionService The conversion service
* @param mediaTypes The media types
*/
AbstractRoute(MethodExecutionHandle targetMethod, ConversionService conversionService, List mediaTypes) {
this.targetMethod = targetMethod;
this.conversionService = conversionService;
this.consumesMediaTypes = mediaTypes;
for (Argument> argument : targetMethod.getArguments()) {
if (argument.getAnnotationMetadata().hasAnnotation(Body.class)) {
this.bodyArgument = argument;
}
}
}
@Override
public Route consumes(MediaType... mediaTypes) {
if (mediaTypes != null) {
this.consumesMediaTypes = List.of(mediaTypes);
}
return this;
}
@Override
public List getConsumes() {
return consumesMediaTypes;
}
@Override
public Route consumesAll() {
this.consumesMediaTypes = Collections.emptyList();
return this;
}
@Override
public Route where(Predicate> condition) {
if (condition != null) {
conditions.add(condition);
}
return this;
}
@Override
public Route body(String argument) {
this.bodyArgumentName = argument;
return this;
}
@Override
public Route body(Argument> argument) {
this.bodyArgument = argument;
return this;
}
@Override
public Route produces(MediaType... mediaType) {
if (mediaType != null) {
this.producesMediaTypes = List.of(mediaType);
}
return this;
}
@Override
public List getProduces() {
return producesMediaTypes;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AbstractRoute that)) {
return false;
}
return Objects.equals(consumesMediaTypes, that.consumesMediaTypes) &&
Objects.equals(producesMediaTypes, that.producesMediaTypes);
}
@Override
public int hashCode() {
return ObjectUtils.hash(consumesMediaTypes, producesMediaTypes);
}
}
/**
* Default Error Route.
*/
final class DefaultErrorRoute extends AbstractRoute implements ErrorRoute {
private final Class extends Throwable> error;
private final Class> originatingClass;
/**
* @param error The throwable
* @param targetMethod The target method execution handle
* @param conversionService The conversion service
*/
public DefaultErrorRoute(Class extends Throwable> error, MethodExecutionHandle targetMethod, ConversionService conversionService) {
this(null, error, targetMethod, conversionService);
}
/**
* @param originatingClass The originating class
* @param error The throwable
* @param targetMethod The target method execution handle
* @param conversionService The conversion service
*/
public DefaultErrorRoute(Class> originatingClass,
Class extends Throwable> error,
MethodExecutionHandle targetMethod,
ConversionService conversionService) {
super(targetMethod, conversionService, Collections.emptyList());
this.originatingClass = originatingClass;
this.error = error;
}
@Override
public ErrorRouteInfo toRouteInfo() {
return new DefaultErrorRouteInfo<>(
originatingClass,
error,
targetMethod,
bodyArgumentName,
bodyArgument,
consumesMediaTypes,
producesMediaTypes,
conditions,
conversionService,
messageBodyHandlerRegistry);
}
@Override
@Nullable
public Class> originatingType() {
return originatingClass;
}
@Override
public Class extends Throwable> exceptionType() {
return error;
}
@Override
public ErrorRoute consumes(MediaType... mediaType) {
return (ErrorRoute) super.consumes(mediaType);
}
@Override
public ErrorRoute produces(MediaType... mediaType) {
return (ErrorRoute) super.produces(mediaType);
}
@Override
public Route consumesAll() {
super.consumesAll();
return this;
}
@Override
public ErrorRoute nest(Runnable nested) {
return this;
}
@Override
public ErrorRoute where(Predicate> condition) {
return (ErrorRoute) super.where(condition);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
DefaultErrorRoute that = (DefaultErrorRoute) o;
return error.equals(that.error) &&
Objects.equals(originatingClass, that.originatingClass);
}
@Override
public int hashCode() {
return ObjectUtils.hash(super.hashCode(), error, originatingClass);
}
@Override
public String toString() {
return ' ' + error.getSimpleName()
+ " -> " + targetMethod.getDeclaringType().getSimpleName()
+ '#' + targetMethod;
}
}
/**
* Represents a route for an {@link io.micronaut.http.HttpStatus} code.
*/
final class DefaultStatusRoute extends AbstractRoute implements StatusRoute {
private final int statusCode;
private final Class> originatingClass;
/**
* @param status The HTTP Status
* @param targetMethod The target method execution handle
* @param conversionService The conversion service
*/
public DefaultStatusRoute(HttpStatus status, MethodExecutionHandle targetMethod, ConversionService conversionService) {
this(null, status, targetMethod, conversionService);
}
/**
* @param originatingClass The originating class
* @param status The HTTP Status
* @param targetMethod The target method execution handle
* @param conversionService The conversion service
*/
public DefaultStatusRoute(Class> originatingClass, HttpStatus status, MethodExecutionHandle targetMethod, ConversionService conversionService) {
super(targetMethod, conversionService, Collections.emptyList());
this.originatingClass = originatingClass;
this.statusCode = status.getCode();
}
@Override
public StatusRouteInfo toRouteInfo() {
return new DefaultStatusRouteInfo<>(
originatingClass,
statusCode,
targetMethod,
bodyArgumentName,
bodyArgument,
consumesMediaTypes,
producesMediaTypes,
conditions,
conversionService,
messageBodyHandlerRegistry
);
}
@Override
@Nullable
public Class> originatingType() {
return originatingClass;
}
@Override
public HttpStatus status() {
return HttpStatus.valueOf(statusCode);
}
@Override
public int statusCode() {
return statusCode;
}
@Override
public StatusRoute consumes(MediaType... mediaType) {
return this;
}
@Override
public Route consumesAll() {
return this;
}
@Override
public StatusRoute nest(Runnable nested) {
return this;
}
@Override
public StatusRoute where(Predicate> condition) {
return (StatusRoute) super.where(condition);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DefaultStatusRoute that)) {
return false;
}
if (!super.equals(o)) {
return false;
}
return statusCode == that.statusCode &&
Objects.equals(originatingClass, that.originatingClass);
}
@Override
public int hashCode() {
return ObjectUtils.hash(super.hashCode(), statusCode, originatingClass);
}
}
/**
* The default route impl.
*/
final class DefaultUriRoute extends AbstractRoute implements UriRoute {
final String httpMethodName;
final HttpMethod httpMethod;
final UriMatchTemplate uriMatchTemplate;
final List nestedRoutes = new ArrayList<>(2);
private Integer port;
private final RouteExecutorSelector executorSelector = new RouteExecutorSelector();
/**
* @param httpMethod The HTTP method
* @param uriTemplate The URI Template as a {@link CharSequence}
* @param targetMethod The target method execution handle
* @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods
* @param conversionService The conversion service
*/
DefaultUriRoute(HttpMethod httpMethod,
CharSequence uriTemplate,
MethodExecutionHandle targetMethod,
String httpMethodName,
ConversionService conversionService) {
this(httpMethod, uriTemplate, MediaType.APPLICATION_JSON_TYPE, targetMethod, httpMethodName, conversionService);
}
/**
* @param httpMethod The HTTP method
* @param uriTemplate The URI Template as a {@link CharSequence}
* @param mediaType The Media type
* @param targetMethod The target method execution handle
* @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods
* @param conversionService The conversion service
*/
DefaultUriRoute(HttpMethod httpMethod,
CharSequence uriTemplate,
MediaType mediaType,
MethodExecutionHandle targetMethod,
String httpMethodName,
ConversionService conversionService) {
this(httpMethod, new UriMatchTemplate(uriTemplate), Collections.singletonList(mediaType), targetMethod, httpMethodName, conversionService);
}
/**
* @param httpMethod The HTTP method
* @param uriTemplate The URI Template as a {@link CharSequence}
* @param mediaTypes The Media types
* @param targetMethod The target method execution handle
* @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods
* @param conversionService The conversion service
*/
DefaultUriRoute(HttpMethod httpMethod,
CharSequence uriTemplate,
List mediaTypes,
MethodExecutionHandle targetMethod,
String httpMethodName,
ConversionService conversionService) {
this(httpMethod, new UriMatchTemplate(uriTemplate), mediaTypes, targetMethod, httpMethodName, conversionService);
}
/**
* @param httpMethod The HTTP method
* @param uriTemplate The URI Template as a {@link UriMatchTemplate}
* @param targetMethod The target method execution handle
* @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods
* @param conversionService The conversion service
*/
DefaultUriRoute(HttpMethod httpMethod,
UriMatchTemplate uriTemplate,
MethodExecutionHandle targetMethod,
String httpMethodName,
ConversionService conversionService) {
this(httpMethod, uriTemplate, Collections.singletonList(MediaType.APPLICATION_JSON_TYPE), targetMethod, httpMethodName, conversionService);
}
/**
* @param httpMethod The HTTP method
* @param uriTemplate The URI Template as a {@link UriMatchTemplate}
* @param mediaTypes The media types
* @param targetMethod The target method execution handle
* @param httpMethodName The actual name of the method - may differ from {@link HttpMethod#name()} for non-standard http methods
* @param conversionService The conversion service
*/
DefaultUriRoute(HttpMethod httpMethod,
UriMatchTemplate uriTemplate,
List mediaTypes,
MethodExecutionHandle targetMethod,
String httpMethodName,
ConversionService conversionService) {
super(targetMethod, conversionService, mediaTypes);
this.httpMethod = httpMethod;
this.uriMatchTemplate = uriTemplate;
this.httpMethodName = httpMethodName;
if (targetMethod.isPresent(RouteCondition.class, AnnotationMetadata.VALUE_MEMBER)) {
AnnotationValue annotation = targetMethod.getAnnotation(RouteCondition.class);
if (annotation instanceof EvaluatedAnnotationValue) {
where(request -> annotation.booleanValue().orElse(false));
}
}
}
@Override
public UriRouteInfo toRouteInfo() {
return new DefaultUrlRouteInfo<>(
httpMethod,
uriMatchTemplate,
defaultCharset,
targetMethod,
bodyArgumentName,
bodyArgument,
consumesMediaTypes,
producesMediaTypes,
conditions,
port,
conversionService,
executorSelector,
messageBodyHandlerRegistry
);
}
@Override
public String getHttpMethodName() {
return httpMethodName;
}
@Override
public String toString() {
return getHttpMethodName() + ' '
+ uriMatchTemplate
+ " -> " + targetMethod.getDeclaringType().getSimpleName()
+ '#' + targetMethod.getName()
+ " (" + String.join(",", consumesMediaTypes) + ')';
}
@Override
public HttpMethod getHttpMethod() {
return httpMethod;
}
@Override
public UriRoute body(String argument) {
return (UriRoute) super.body(argument);
}
@Override
public UriRoute exposedPort(int port) {
this.port = port;
where(httpRequest -> httpRequest.getServerAddress().getPort() == port);
DefaultRouteBuilder.this.exposedPorts.add(port);
return this;
}
@Override
public Integer getPort() {
return port;
}
@Override
public UriRoute consumes(MediaType... mediaTypes) {
return (UriRoute) super.consumes(mediaTypes);
}
@Override
public UriRoute produces(MediaType... mediaType) {
return (UriRoute) super.produces(mediaType);
}
@Override
public UriRoute consumesAll() {
return (UriRoute) super.consumesAll();
}
@Override
public UriRoute nest(Runnable nested) {
DefaultUriRoute previous = DefaultRouteBuilder.this.currentParentRoute;
DefaultRouteBuilder.this.currentParentRoute = this;
try {
nested.run();
} finally {
DefaultRouteBuilder.this.currentParentRoute = previous;
}
return this;
}
@Override
public UriRoute where(Predicate> condition) {
return (UriRoute) super.where(condition);
}
@Override
public UriMatchTemplate getUriMatchTemplate() {
return this.uriMatchTemplate;
}
@Override
public int compareTo(@NonNull UriRoute o) {
return uriMatchTemplate.compareTo(o.getUriMatchTemplate());
}
private final class RouteExecutorSelector implements ExecutorSelector {
@Override
public Optional select(MethodReference, ?> method, ThreadSelection threadSelection) {
if (DefaultRouteBuilder.this.executorSelector != null) {
return DefaultRouteBuilder.this.executorSelector.select(targetMethod.getExecutableMethod(), threadSelection);
} else {
return Optional.empty();
}
}
@Override
public Optional select(String name) {
if (DefaultRouteBuilder.this.executorSelector != null) {
return DefaultRouteBuilder.this.executorSelector.select(name);
} else {
return Optional.empty();
}
}
}
}
/**
* Define a single route.
*/
final class DefaultSingleRoute extends DefaultResourceRoute {
/**
* @param resourceRoutes The resource routes
* @param getRoute The default Uri route
*/
DefaultSingleRoute(Map resourceRoutes, DefaultUriRoute getRoute) {
super(resourceRoutes, getRoute);
}
/**
* @param type The class
*/
DefaultSingleRoute(Class> type) {
super(type);
}
@Override
protected ResourceRoute newResourceRoute(Map newMap, DefaultUriRoute getRoute) {
return new DefaultSingleRoute(newMap, getRoute);
}
@Override
protected DefaultUriRoute buildGetRoute(Class> type, Map routeMap) {
DefaultUriRoute getRoute = (DefaultUriRoute) DefaultRouteBuilder.this.GET(type);
routeMap.put(
HttpMethod.GET, getRoute
);
return getRoute;
}
@Override
protected void buildRemainingRoutes(Class> type, Map routeMap) {
// POST /foo
routeMap.put(
HttpMethod.POST, DefaultRouteBuilder.this.POST(type)
);
// DELETE /foo
routeMap.put(
HttpMethod.DELETE, DefaultRouteBuilder.this.DELETE(type)
);
// PATCH /foo
routeMap.put(
HttpMethod.PATCH, DefaultRouteBuilder.this.PATCH(type)
);
// PUT /foo
routeMap.put(
HttpMethod.PUT, DefaultRouteBuilder.this.PUT(type)
);
}
}
/**
* Default resource route.
*/
class DefaultResourceRoute implements ResourceRoute {
private final Map resourceRoutes;
private final DefaultUriRoute getRoute;
/**
* @param resourceRoutes The resource routes
* @param getRoute The default Uri route
*/
DefaultResourceRoute(Map resourceRoutes, DefaultUriRoute getRoute) {
this.resourceRoutes = resourceRoutes;
this.getRoute = getRoute;
}
/**
* @param type The class
*/
DefaultResourceRoute(Class> type) {
this.resourceRoutes = new LinkedHashMap<>();
// GET /foo/1
Map routeMap = this.resourceRoutes;
this.getRoute = buildGetRoute(type, routeMap);
buildRemainingRoutes(type, routeMap);
}
@Override
public RouteInfo toRouteInfo() {
throw new IllegalStateException("Not implemented!");
}
@Override
public ResourceRoute consumes(MediaType... mediaTypes) {
if (mediaTypes != null) {
for (Route route : resourceRoutes.values()) {
route.consumes(mediaTypes);
}
}
return this;
}
@Override
public Route consumesAll() {
return consumes(MediaType.EMPTY_ARRAY);
}
@Override
public ResourceRoute nest(Runnable nested) {
DefaultUriRoute previous = DefaultRouteBuilder.this.currentParentRoute;
DefaultRouteBuilder.this.currentParentRoute = getRoute;
try {
nested.run();
} finally {
DefaultRouteBuilder.this.currentParentRoute = previous;
}
return this;
}
@Override
public ResourceRoute where(Predicate> condition) {
for (Route route : resourceRoutes.values()) {
route.where(condition);
}
return this;
}
@Override
public ResourceRoute produces(MediaType... mediaType) {
if (mediaType != null) {
for (Route route : resourceRoutes.values()) {
route.produces(mediaType);
}
}
return this;
}
@Override
public ResourceRoute body(String argument) {
return this;
}
@Override
public Route body(Argument> argument) {
return this;
}
@Override
public ResourceRoute readOnly(boolean readOnly) {
List excluded = Arrays.asList(HttpMethod.DELETE, HttpMethod.PATCH, HttpMethod.POST, HttpMethod.PUT);
return handleExclude(excluded);
}
@Override
public ResourceRoute exclude(HttpMethod... methods) {
return handleExclude(Arrays.asList(methods));
}
/**
* @param newMap New map info
* @param getRoute The default route
*
* @return The {@link ResourceRoute}
*/
protected ResourceRoute newResourceRoute(Map newMap, DefaultUriRoute getRoute) {
return new DefaultResourceRoute(newMap, getRoute);
}
/**
* @param type The class
* @param routeMap The route info
*
* @return The {@link DefaultUriRoute}
*/
protected DefaultUriRoute buildGetRoute(Class> type, Map routeMap) {
DefaultUriRoute getRoute = (DefaultUriRoute) DefaultRouteBuilder.this.GET(type, ID);
routeMap.put(
HttpMethod.GET, getRoute
);
return getRoute;
}
/**
* Build the remaining routes.
*
* @param type The class
* @param routeMap The route info
*/
protected void buildRemainingRoutes(Class> type, Map routeMap) {
// GET /foo
routeMap.put(
HttpMethod.GET, DefaultRouteBuilder.this.GET(type)
);
// POST /foo
routeMap.put(
HttpMethod.POST, DefaultRouteBuilder.this.POST(type)
);
// DELETE /foo/1
routeMap.put(
HttpMethod.DELETE, DefaultRouteBuilder.this.DELETE(type, ID)
);
// PATCH /foo/1
routeMap.put(
HttpMethod.PATCH, DefaultRouteBuilder.this.PATCH(type, ID)
);
// PUT /foo/1
routeMap.put(
HttpMethod.PUT, DefaultRouteBuilder.this.PUT(type, ID)
);
}
private ResourceRoute handleExclude(List excluded) {
var newMap = new LinkedHashMap();
this.resourceRoutes.forEach((key, value) -> {
if (excluded.contains(key)) {
DefaultRouteBuilder.this.uriRoutes.remove(value);
} else {
newMap.put(key, value);
}
});
return newResourceRoute(newMap, getRoute);
}
}
}