package io.micronaut.web.router;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
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.AnnotationMetadataResolver;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.ReturnType;
import io.micronaut.core.util.ArrayUtils;
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.Consumes;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.filter.HttpFilter;
import io.micronaut.http.uri.UriMatchInfo;
import io.micronaut.http.uri.UriMatchTemplate;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.MethodExecutionHandle;
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.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
public abstract class DefaultRouteBuilder implements RouteBuilder {
public static final UriNamingStrategy CAMEL_CASE_NAMING_STRATEGY = new UriNamingStrategy() {
};
protected static final Logger LOG = LoggerFactory.getLogger(DefaultRouteBuilder.class);
static final Object NO_VALUE = new Object();
protected final ExecutionHandleLocator executionHandleLocator;
protected final UriNamingStrategy uriNamingStrategy;
protected final ConversionService<?> conversionService;
protected final Charset defaultCharset;
private DefaultUriRoute currentParentRoute = null;
private List<UriRoute> uriRoutes = new ArrayList<>();
private List<StatusRoute> statusRoutes = new ArrayList<>();
private List<ErrorRoute> errorRoutes = new ArrayList<>();
private List<FilterRoute> filterRoutes = new ArrayList<>();
private Set<Integer> exposedPorts = new HashSet<>(5);
public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator) {
this(executionHandleLocator, CAMEL_CASE_NAMING_STRATEGY);
}
public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy) {
this(executionHandleLocator, uriNamingStrategy, ConversionService.SHARED);
}
public DefaultRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService<?> conversionService) {
this.executionHandleLocator = executionHandleLocator;
this.uriNamingStrategy = uriNamingStrategy;
this.conversionService = conversionService;
if (executionHandleLocator instanceof ApplicationContext) {
ApplicationContext applicationContext = (ApplicationContext) executionHandleLocator;
Environment environment = applicationContext.getEnvironment();
defaultCharset = environment.get("micronaut.application.default-charset", Charset.class, StandardCharsets.UTF_8);
} else {
defaultCharset = StandardCharsets.UTF_8;
}
}
@Override
public Set<Integer> getExposedPorts() {
return exposedPorts;
}
@Override
public List<FilterRoute> getFilterRoutes() {
return filterRoutes;
}
@Override
public FilterRoute addFilter(String pathPattern, Supplier<HttpFilter> filter) {
DefaultFilterRoute route = new DefaultFilterRoute(
pathPattern,
filter,
(AnnotationMetadataResolver) executionHandleLocator
);
filterRoutes.add(route);
return route;
}
@Override
public FilterRoute addFilter(String pathPattern, BeanLocator beanLocator, BeanDefinition<? extends HttpFilter> beanDefinition) {
DefaultFilterRoute route = new BeanDefinitionFilterRoute(
pathPattern,
beanLocator,
beanDefinition
);
filterRoutes.add(route);
return route;
}
@Override
public List<StatusRoute> getStatusRoutes() {
return Collections.unmodifiableList(statusRoutes);
}
@Override
public List<ErrorRoute> getErrorRoutes() {
return Collections.unmodifiableList(errorRoutes);
}
@Override
public List<UriRoute> 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<MethodExecutionHandle<?, Object>> executionHandle = executionHandleLocator.findExecutionHandle(type, method, parameterTypes);
MethodExecutionHandle<?, Object> 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<MethodExecutionHandle<?, Object>> executionHandle = executionHandleLocator.findExecutionHandle(type, method, parameterTypes);
MethodExecutionHandle<?, Object> 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<MethodExecutionHandle<?, Object>> executionHandle = executionHandleLocator.findExecutionHandle(type, method, parameterTypes);
MethodExecutionHandle<?, Object> 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<MethodExecutionHandle<?, Object>> executionHandle = executionHandleLocator.findExecutionHandle(type, method, parameterTypes);
MethodExecutionHandle<?, Object> 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);
}
protected UriRoute buildRoute(HttpMethod httpMethod, String uri, Class<?> type, String method, Class... parameterTypes) {
Optional<? extends MethodExecutionHandle<?, Object>> executionHandle = executionHandleLocator.findExecutionHandle(type, method, parameterTypes);
MethodExecutionHandle<?, Object> executableHandle = executionHandle.orElseThrow(() ->
new RoutingException("No such route: " + type.getName() + "." + method)
);
return buildRoute(httpMethod, uri, executableHandle);
}
protected UriRoute buildRoute(HttpMethod httpMethod, String uri, MethodExecutionHandle<?, Object> executableHandle) {
return buildRoute(httpMethod.name(), httpMethod, uri, executableHandle);
}
private UriRoute buildRoute(String httpMethodName, HttpMethod httpMethod, String uri, MethodExecutionHandle<?, Object> executableHandle) {
UriRoute route;
if (currentParentRoute != null) {
route = new DefaultUriRoute(httpMethod, currentParentRoute.uriMatchTemplate.nest(uri), executableHandle, httpMethodName);
currentParentRoute.nestedRoutes.add((DefaultUriRoute) route);
} else {
route = new DefaultUriRoute(httpMethod, uri, executableHandle, httpMethodName);
}
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);
}
protected UriRoute buildBeanRoute(String httpMethodName, HttpMethod httpMethod, String uri, BeanDefinition<?> beanDefinition, ExecutableMethod<?, ?> method) {
MethodExecutionHandle<?, Object> executionHandle = executionHandleLocator
.createExecutionHandle(beanDefinition, (ExecutableMethod<Object, ?>) method);
return buildRoute(httpMethodName, httpMethod, uri, executionHandle);
}
abstract class AbstractRoute implements MethodBasedRoute, RouteInfo<Object> {
protected final List<Predicate<HttpRequest<?>>> conditions = new ArrayList<>();
protected final MethodExecutionHandle<?, ?> targetMethod;
protected final ConversionService<?> conversionService;
protected List<MediaType> consumesMediaTypes;
protected List<MediaType> producesMediaTypes;
protected String bodyArgumentName;
protected Argument<?> bodyArgument;
private final boolean isVoid;
private final boolean suspended;
private final boolean reactive;
private final boolean single;
private final boolean async;
private final boolean specifiedSingle;
AbstractRoute(MethodExecutionHandle targetMethod, ConversionService<?> conversionService, List<MediaType> mediaTypes) {
this.targetMethod = targetMethod;
this.conversionService = conversionService;
this.consumesMediaTypes = mediaTypes;
MediaType[] types = MediaType.of(targetMethod.stringValues(Produces.class));
if (ArrayUtils.isNotEmpty(types)) {
this.producesMediaTypes = Arrays.asList(types);
}
types = MediaType.of(targetMethod.stringValues(Consumes.class));
if (ArrayUtils.isNotEmpty(types)) {
this.consumesMediaTypes = Arrays.asList(types);
}
suspended = targetMethod.getExecutableMethod().isSuspend();
reactive = RouteInfo.super.isReactive();
async = RouteInfo.super.isAsync();
single = RouteInfo.super.isSingleResult();
isVoid = RouteInfo.super.isVoid();
specifiedSingle = RouteInfo.super.isSpecifiedSingle();
for (Argument argument : targetMethod.getArguments()) {
if (argument.getAnnotationMetadata().hasAnnotation(Body.class)) {
this.bodyArgument = argument;
}
}
}
@NonNull
@Override
public AnnotationMetadata getAnnotationMetadata() {
return targetMethod.getAnnotationMetadata();
}
@Override
public ReturnType<?> getReturnType() {
return targetMethod.getReturnType();
}
@Override
public boolean isSuspended() {
return suspended;
}
@Override
public boolean isReactive() {
return reactive;
}
@Override
public boolean isSingleResult() {
return single;
}
@Override
public boolean isSpecifiedSingle() {
return specifiedSingle;
}
@Override
public boolean isAsync() {
return async;
}
@Override
public boolean isVoid() {
return isVoid;
}
@Override
public Route consumes(MediaType... mediaTypes) {
if (mediaTypes != null) {
this.consumesMediaTypes = Collections.unmodifiableList(Arrays.asList(mediaTypes));
}
return this;
}
@Override
public List<MediaType> getConsumes() {
if (consumesMediaTypes != null) {
return consumesMediaTypes;
} else {
return Collections.emptyList();
}
}
@Override
public Route consumesAll() {
this.consumesMediaTypes = Collections.emptyList();
return this;
}
@Override
public Route where(Predicate<HttpRequest<?>> 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 = Arrays.asList(mediaType);
}
return this;
}
@Override
public List<MediaType> getProduces() {
if (producesMediaTypes != null) {
return Collections.unmodifiableList(producesMediaTypes);
} else {
return DEFAULT_PRODUCES;
}
}
@Override
public MethodExecutionHandle getTargetMethod() {
return this.targetMethod;
}
protected boolean permitsRequestBody() {
return true;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AbstractRoute)) {
return false;
}
AbstractRoute that = (AbstractRoute) o;
return Objects.equals(consumesMediaTypes, that.consumesMediaTypes) &&
Objects.equals(producesMediaTypes, that.producesMediaTypes);
}
@Override
public int hashCode() {
return Objects.hash(consumesMediaTypes, producesMediaTypes);
}
}
class DefaultErrorRoute extends AbstractRoute implements ErrorRoute {
private final Class<? extends Throwable> error;
private final Class originatingClass;
public DefaultErrorRoute(Class<? extends Throwable> error, MethodExecutionHandle targetMethod, ConversionService<?> conversionService) {
this(null, error, targetMethod, conversionService);
}
public DefaultErrorRoute(
Class originatingClass, Class<? extends Throwable> error,
MethodExecutionHandle targetMethod,
ConversionService<?> conversionService) {
super(targetMethod, conversionService, Collections.emptyList());
this.originatingClass = originatingClass;
this.error = error;
}
@Override
@Nullable
public Class<?> originatingType() {
return originatingClass;
}
@Override
public Class<? extends Throwable> exceptionType() {
return error;
}
@SuppressWarnings("unchecked")
@Override
public <T> Optional<RouteMatch<T>> match(Class originatingClass, Throwable exception) {
if (originatingClass == this.originatingClass && error.isInstance(exception)) {
return Optional.of(new ErrorRouteMatch(exception, this, conversionService));
}
return Optional.empty();
}
@SuppressWarnings("unchecked")
@Override
public <T> Optional<RouteMatch<T>> match(Throwable exception) {
if (originatingClass == null && error.isInstance(exception)) {
return Optional.of(new ErrorRouteMatch(exception, this, conversionService));
}
return Optional.empty();
}
@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<HttpRequest<?>> 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 Objects.hash(super.hashCode(), error, originatingClass);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
return builder.append(' ')
.append(error.getSimpleName())
.append(" -> ")
.append(targetMethod.getDeclaringType().getSimpleName())
.append('#')
.append(targetMethod)
.toString();
}
}
class DefaultStatusRoute extends AbstractRoute implements StatusRoute {
private final HttpStatus status;
private final Class originatingClass;
public DefaultStatusRoute(HttpStatus status, MethodExecutionHandle targetMethod, ConversionService<?> conversionService) {
this(null, status, targetMethod, conversionService);
}
public DefaultStatusRoute(Class originatingClass, HttpStatus status, MethodExecutionHandle targetMethod, ConversionService<?> conversionService) {
super(targetMethod, conversionService, Collections.emptyList());
this.originatingClass = originatingClass;
this.status = status;
}
@Override
@Nullable
public Class<?> originatingType() {
return originatingClass;
}
@Override
public HttpStatus status() {
return status;
}
@SuppressWarnings("unchecked")
@Override
public <T> Optional<RouteMatch<T>> match(Class originatingClass, HttpStatus status) {
if (originatingClass == this.originatingClass && this.status == status) {
return Optional.of(new StatusRouteMatch(status, this, conversionService));
}
return Optional.empty();
}
@SuppressWarnings("unchecked")
@Override
public <T> Optional<RouteMatch<T>> match(HttpStatus status) {
if (this.originatingClass == null && this.status == status) {
return Optional.of(new StatusRouteMatch(status, this, conversionService));
}
return Optional.empty();
}
@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<HttpRequest<?>> condition) {
return (StatusRoute) super.where(condition);
}
public HttpStatus getStatus() {
return status;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DefaultStatusRoute)) {
return false;
}
if (!super.equals(o)) {
return false;
}
DefaultStatusRoute that = (DefaultStatusRoute) o;
return status == that.status &&
Objects.equals(originatingClass, that.originatingClass);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), status, originatingClass);
}
}
class DefaultUriRoute extends AbstractRoute implements UriRoute {
final String httpMethodName;
final HttpMethod httpMethod;
final UriMatchTemplate uriMatchTemplate;
final List<DefaultUriRoute> nestedRoutes = new ArrayList<>(2);
private Integer port;
DefaultUriRoute(HttpMethod httpMethod, CharSequence uriTemplate, MethodExecutionHandle targetMethod) {
this(httpMethod, uriTemplate, targetMethod, httpMethod.name());
}
DefaultUriRoute(HttpMethod httpMethod, CharSequence uriTemplate, MethodExecutionHandle targetMethod, String httpMethodName) {
this(httpMethod, uriTemplate, MediaType.APPLICATION_JSON_TYPE, targetMethod, httpMethodName);
}
DefaultUriRoute(HttpMethod httpMethod, CharSequence uriTemplate, MediaType mediaType, MethodExecutionHandle targetMethod) {
this(httpMethod, uriTemplate, mediaType, targetMethod, httpMethod.name());
}
DefaultUriRoute(HttpMethod httpMethod, CharSequence uriTemplate, MediaType mediaType, MethodExecutionHandle targetMethod, String httpMethodName) {
this(httpMethod, new UriMatchTemplate(uriTemplate), Collections.singletonList(mediaType), targetMethod, httpMethodName);
}
DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, MethodExecutionHandle targetMethod) {
this(httpMethod, uriTemplate, targetMethod, httpMethod.name());
}
DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, MethodExecutionHandle targetMethod, String httpMethodName) {
this(httpMethod, uriTemplate, Collections.singletonList(MediaType.APPLICATION_JSON_TYPE), targetMethod, httpMethodName);
}
DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, List<MediaType> mediaTypes, MethodExecutionHandle targetMethod) {
this(httpMethod, uriTemplate, mediaTypes, targetMethod, httpMethod.name());
}
DefaultUriRoute(HttpMethod httpMethod, UriMatchTemplate uriTemplate, List<MediaType> mediaTypes, MethodExecutionHandle targetMethod, String httpMethodName) {
super(targetMethod, ConversionService.SHARED, mediaTypes);
this.httpMethod = httpMethod;
this.uriMatchTemplate = uriTemplate;
this.httpMethodName = httpMethodName;
}
@Override
public String getHttpMethodName() {
return httpMethodName;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getHttpMethodName());
return builder.append(' ')
.append(uriMatchTemplate)
.append(" -> ")
.append(targetMethod.getDeclaringType().getSimpleName())
.append('#')
.append(targetMethod.getName())
.append(" (")
.append(String.join(",", consumesMediaTypes))
.append(")")
.toString();
}
@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<HttpRequest<?>> condition) {
return (UriRoute) super.where(condition);
}
@SuppressWarnings("unchecked")
@Override
public Optional<UriRouteMatch> match(String uri) {
Optional<UriMatchInfo> matchInfo = uriMatchTemplate.match(uri);
return matchInfo.map(info -> new DefaultUriRouteMatch(info, this, defaultCharset, conversionService));
}
@Override
public UriMatchTemplate getUriMatchTemplate() {
return this.uriMatchTemplate;
}
@Override
public int compareTo(UriRoute o) {
return uriMatchTemplate.compareTo(o.getUriMatchTemplate());
}
@Override
protected boolean permitsRequestBody() {
return HttpMethod.permitsRequestBody(httpMethod);
}
}
class DefaultSingleRoute extends DefaultResourceRoute {
DefaultSingleRoute(Map<HttpMethod, Route> resourceRoutes, DefaultUriRoute getRoute) {
super(resourceRoutes, getRoute);
}
DefaultSingleRoute(Class type) {
super(type);
}
@Override
protected ResourceRoute newResourceRoute(Map<HttpMethod, Route> newMap, DefaultUriRoute getRoute) {
return new DefaultSingleRoute(newMap, getRoute);
}
@Override
protected DefaultUriRoute buildGetRoute(Class type, Map<HttpMethod, Route> routeMap) {
DefaultUriRoute getRoute = (DefaultUriRoute) DefaultRouteBuilder.this.GET(type);
routeMap.put(
HttpMethod.GET, getRoute
);
return getRoute;
}
@Override
protected void buildRemainingRoutes(Class type, Map<HttpMethod, Route> routeMap) {
routeMap.put(
HttpMethod.POST, DefaultRouteBuilder.this.POST(type)
);
routeMap.put(
HttpMethod.DELETE, DefaultRouteBuilder.this.DELETE(type)
);
routeMap.put(
HttpMethod.PATCH, DefaultRouteBuilder.this.PATCH(type)
);
routeMap.put(
HttpMethod.PUT, DefaultRouteBuilder.this.PUT(type)
);
}
}
class DefaultResourceRoute implements ResourceRoute {
private final Map<HttpMethod, Route> resourceRoutes;
private final DefaultUriRoute getRoute;
DefaultResourceRoute(Map<HttpMethod, Route> resourceRoutes, DefaultUriRoute getRoute) {
this.resourceRoutes = resourceRoutes;
this.getRoute = getRoute;
}
DefaultResourceRoute(Class type) {
this.resourceRoutes = new LinkedHashMap<>();
Map<HttpMethod, Route> routeMap = this.resourceRoutes;
this.getRoute = buildGetRoute(type, routeMap);
buildRemainingRoutes(type, routeMap);
}
@Override
public ResourceRoute consumes(MediaType... mediaTypes) {
if (mediaTypes != null) {
for (Route route : resourceRoutes.values()) {
route.produces(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<HttpRequest<?>> 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<HttpMethod> 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));
}
protected ResourceRoute newResourceRoute(Map<HttpMethod, Route> newMap, DefaultUriRoute getRoute) {
return new DefaultResourceRoute(newMap, getRoute);
}
protected DefaultUriRoute buildGetRoute(Class type, Map<HttpMethod, Route> routeMap) {
DefaultUriRoute getRoute = (DefaultUriRoute) DefaultRouteBuilder.this.GET(type, ID);
routeMap.put(
HttpMethod.GET, getRoute
);
return getRoute;
}
protected void buildRemainingRoutes(Class type, Map<HttpMethod, Route> routeMap) {
routeMap.put(
HttpMethod.GET, DefaultRouteBuilder.this.GET(type)
);
routeMap.put(
HttpMethod.POST, DefaultRouteBuilder.this.POST(type)
);
routeMap.put(
HttpMethod.DELETE, DefaultRouteBuilder.this.DELETE(type, ID)
);
routeMap.put(
HttpMethod.PATCH, DefaultRouteBuilder.this.PATCH(type, ID)
);
routeMap.put(
HttpMethod.PUT, DefaultRouteBuilder.this.PUT(type, ID)
);
}
private ResourceRoute handleExclude(List<HttpMethod> excluded) {
Map<HttpMethod, Route> 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);
}
}
}