All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.web.router.AnnotatedMethodRouteBuilder Maven / Gradle / Ivy

There is a newer version: 4.6.5
Show newest version
/*
 * 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.ExecutionHandleLocator;
import io.micronaut.context.processor.ExecutableMethodProcessor;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpMethod;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.CustomHttpMethod;
import io.micronaut.http.annotation.Delete;
import io.micronaut.http.annotation.Error;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Head;
import io.micronaut.http.annotation.HttpMethodMapping;
import io.micronaut.http.annotation.Options;
import io.micronaut.http.annotation.Patch;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.annotation.Put;
import io.micronaut.http.annotation.Trace;
import io.micronaut.http.annotation.UriMapping;
import io.micronaut.http.uri.UriTemplate;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import jakarta.inject.Singleton;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

/**
 * Responsible for building {@link Route} instances for the annotations found in the {@code io.micronaut.http.annotation}
 * package.
 *
 * @author Graeme Rocher
 * @since 1.0
 */
@Singleton
public class AnnotatedMethodRouteBuilder extends DefaultRouteBuilder implements ExecutableMethodProcessor {

    private static final MediaType[] DEFAULT_MEDIA_TYPES = {MediaType.APPLICATION_JSON_TYPE};
    private final Map, Consumer> httpMethodsHandlers = new LinkedHashMap<>();

    /**
     * @param executionHandleLocator The execution handler locator
     * @param uriNamingStrategy The URI naming strategy
     * @param conversionService The conversion service
     */
    public AnnotatedMethodRouteBuilder(ExecutionHandleLocator executionHandleLocator, UriNamingStrategy uriNamingStrategy, ConversionService conversionService) {
        super(executionHandleLocator, uriNamingStrategy, conversionService);
        httpMethodsHandlers.put(Get.class, (RouteDefinition definition) -> {
            final BeanDefinition bean = definition.beanDefinition;
            final ExecutableMethod method = definition.executableMethod;
            Set uris = this.resolveUrisMapping(Get.class, method);
            for (String uri: uris) {
                MediaType[] produces = resolveProduces(method);
                UriRoute route = GET(resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method).produces(produces);

                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
                if (method.booleanValue(Get.class, "headRoute").orElse(true)) {
                    route = HEAD(resolveUri(bean, uri,
                            method,
                            uriNamingStrategy),
                            bean,
                            method).produces(produces);
                    if (definition.port > -1) {
                        route.exposedPort(definition.port);
                    }
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Created Route: {}", route);
                    }
                }
            }
        });

        httpMethodsHandlers.put(Post.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;
            Set uris = this.resolveUrisMapping(Post.class, method);
            for (String uri: uris) {
                MediaType[] consumes = resolveConsumes(method);
                MediaType[] produces = resolveProduces(method);
                UriRoute route = POST(resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method);
                route = route.consumes(consumes).produces(produces);
                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        });

        httpMethodsHandlers.put(CustomHttpMethod.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;

            Set uris = this.resolveUrisMapping(CustomHttpMethod.class, method);
            for (String uri: uris) {
                MediaType[] consumes = resolveConsumes(method);
                MediaType[] produces = resolveProduces(method);
                String methodName = method.stringValue(CustomHttpMethod.class, "method").get();
                UriRoute route = buildBeanRoute(methodName, HttpMethod.CUSTOM, resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method);
                route = route.consumes(consumes).produces(produces);
                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        });

        httpMethodsHandlers.put(Put.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;

            Set uris = this.resolveUrisMapping(Put.class, method);
            for (String uri: uris) {
                MediaType[] consumes = resolveConsumes(method);
                MediaType[] produces = resolveProduces(method);
                UriRoute route = PUT(resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method);
                route = route.consumes(consumes).produces(produces);
                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        });

        httpMethodsHandlers.put(Patch.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;

            Set uris = this.resolveUrisMapping(Patch.class, method);
            for (String uri: uris) {
                MediaType[] consumes = resolveConsumes(method);
                MediaType[] produces = resolveProduces(method);
                UriRoute route = PATCH(resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method);
                route = route.consumes(consumes).produces(produces);
                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        });

        httpMethodsHandlers.put(Delete.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;

            Set uris = this.resolveUrisMapping(Delete.class, method);
            for (String uri: uris) {
                MediaType[] consumes = resolveConsumes(method);
                MediaType[] produces = resolveProduces(method);
                UriRoute route = DELETE(resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method);
                route = route.consumes(consumes).produces(produces);
                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        });


        httpMethodsHandlers.put(Head.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;

            Set uris = this.resolveUrisMapping(Head.class, method);
            for (String uri: uris) {
                UriRoute route = HEAD(resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method);
                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        });

        httpMethodsHandlers.put(Options.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;

            Set uris = this.resolveUrisMapping(Options.class, method);
            for (String uri: uris) {
                MediaType[] consumes = resolveConsumes(method);
                MediaType[] produces = resolveProduces(method);
                UriRoute route = OPTIONS(resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method);
                route = route.consumes(consumes).produces(produces);
                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        });

        httpMethodsHandlers.put(Trace.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;

            Set uris = this.resolveUrisMapping(Trace.class, method);
            for (String uri: uris) {
                UriRoute route = TRACE(resolveUri(bean, uri,
                        method,
                        uriNamingStrategy),
                        bean,
                        method);
                if (definition.port > -1) {
                    route.exposedPort(definition.port);
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        });

        httpMethodsHandlers.put(Error.class, (RouteDefinition definition) -> {
            final ExecutableMethod method = definition.executableMethod;
            final BeanDefinition bean = definition.beanDefinition;

            boolean isGlobal = method.isTrue(Error.class, "global");
                Class declaringType = bean.getBeanType();
                if (method.isPresent(Error.class, "status")) {
                    Optional value = method.enumValue(Error.class, "status", HttpStatus.class);
                    value.ifPresent(httpStatus -> {
                        if (isGlobal) {
                            status(httpStatus, declaringType, method.getMethodName(), method.getArgumentTypes());
                        } else {
                            status(declaringType, httpStatus, declaringType, method.getMethodName(), method.getArgumentTypes());
                        }
                    });
                } else {
                    Class exceptionType = null;
                    if (method.isPresent(Error.class, AnnotationMetadata.VALUE_MEMBER)) {
                        Optional annotationValue = method.classValue(Error.class);
                        if (annotationValue.isPresent() && Throwable.class.isAssignableFrom(annotationValue.get())) {
                            exceptionType = (Class) annotationValue.get();
                        }
                    }
                    if (exceptionType == null) {
                        exceptionType = Arrays.stream(method.getArgumentTypes())
                                .filter(Throwable.class::isAssignableFrom)
                                .findFirst()
                                .orElse(Throwable.class);
                    }

                    if (isGlobal) {
                        error(exceptionType, declaringType, method.getMethodName(), method.getArgumentTypes());
                    } else {
                        error(declaringType, exceptionType, declaringType, method.getMethodName(), method.getArgumentTypes());
                    }
                }
            }
        );
    }

    private Set resolveUrisMapping(Class httpMethod, ExecutableMethod method) {
        Set uris = CollectionUtils.setOf(method.stringValues(httpMethod, "uris"));
        Optional uri = method.stringValue(HttpMethodMapping.class);
        if (uris.isEmpty()) {
            uris.add(uri.orElse(UriMapping.DEFAULT_URI));
        } else {
            uri.ifPresent(uris::add);
        }

        return uris;
    }

    private MediaType[] resolveConsumes(ExecutableMethod method) {
        MediaType[] consumes = MediaType.of(method.stringValues(Consumes.class));
        if (ArrayUtils.isEmpty(consumes)) {
            consumes = DEFAULT_MEDIA_TYPES;
        }
        return consumes;
    }

    private MediaType[] resolveProduces(ExecutableMethod method) {
        MediaType[] produces = MediaType.of(method.stringValues(Produces.class));
        if (ArrayUtils.isEmpty(produces)) {
            produces = DEFAULT_MEDIA_TYPES;
        }
        return produces;
    }

    @Override
    public void process(BeanDefinition beanDefinition, ExecutableMethod method) {
        Optional> actionAnn = method.getAnnotationTypeByStereotype(HttpMethodMapping.class);
        actionAnn.ifPresent(annotationClass -> {
                Consumer handler = httpMethodsHandlers.get(annotationClass);
                if (handler != null) {
                    final int port = beanDefinition.intValue(Controller.class, "port").orElse(-1);
                    handler.accept(new RouteDefinition(beanDefinition, method, port));
                }
            }
        );

        if (actionAnn.isEmpty() && method.isDeclaredAnnotationPresent(UriMapping.class)) {
            Set uris = CollectionUtils.setOf(method.stringValues(UriMapping.class, "uris"));
            uris.add(method.stringValue(UriMapping.class).orElse(UriMapping.DEFAULT_URI));
            for (String uri: uris) {
                MediaType[] produces = MediaType.of(method.stringValues(Produces.class));
                Route route = GET(resolveUri(beanDefinition, uri,
                        method,
                        uriNamingStrategy),
                        method.getDeclaringType(),
                        method.getMethodName(),
                        method.getArgumentTypes()).produces(produces);
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Created Route: {}", route);
                }
            }
        }

    }

    private String resolveUri(BeanDefinition bean, String value, ExecutableMethod method, UriNamingStrategy uriNamingStrategy) {
        UriTemplate rootUri = UriTemplate.of(uriNamingStrategy.resolveUri(bean));
        if (StringUtils.isNotEmpty(value)) {
            return rootUri.nest(value).toString();
        } else {
            return rootUri.nest(uriNamingStrategy.resolveUri(method.getMethodName())).toString();
        }
    }

    /**
     * state class for defining routes.
     */
    private static final class RouteDefinition {
        private final BeanDefinition beanDefinition;
        private final ExecutableMethod executableMethod;
        private final int port;

        public RouteDefinition(BeanDefinition beanDefinition, ExecutableMethod executableMethod, int port) {
            this.beanDefinition = beanDefinition;
            this.executableMethod = executableMethod;
            this.port = port;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy