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

io.inverno.mod.web.compiler.internal.WebRouteInfoFactory Maven / Gradle / Ivy

Go to download

Inverno web compiler module providing an Inverno compiler plugin to aggregate module's web routes and web router configurers into a single web router configurer

There is a newer version: 1.11.0
Show newest version
/*
 * Copyright 2021 Jeremy KUHN
 *
 * 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
 *
 *    http://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.inverno.mod.web.compiler.internal;

import io.inverno.core.compiler.spi.BeanInfo;
import io.inverno.core.compiler.spi.BeanQualifiedName;
import io.inverno.core.compiler.spi.ReporterInfo;
import io.inverno.core.compiler.spi.plugin.PluginContext;
import io.inverno.core.compiler.spi.plugin.PluginExecution;
import io.inverno.mod.base.resource.Resource;
import io.inverno.mod.http.base.Method;
import io.inverno.mod.http.base.ws.WebSocketMessage;
import io.inverno.mod.http.server.ResponseBody;
import io.inverno.mod.web.server.WebResponseBody;
import io.inverno.mod.web.server.annotation.WebController;
import io.inverno.mod.web.server.annotation.WebRoute;
import io.inverno.mod.web.server.annotation.WebRoutes;
import io.inverno.mod.web.server.annotation.WebSocketRoute;
import io.inverno.mod.web.compiler.spi.WebExchangeParameterInfo;
import io.inverno.mod.web.compiler.spi.WebFormParameterInfo;
import io.inverno.mod.web.compiler.spi.WebParameterInfo;
import io.inverno.mod.web.compiler.spi.WebRequestBodyParameterInfo;
import io.inverno.mod.web.compiler.spi.WebResponseBodyInfo;
import io.inverno.mod.web.compiler.spi.WebResponseBodyInfo.ResponseBodyKind;
import io.inverno.mod.web.compiler.spi.WebResponseBodyInfo.ResponseBodyReactiveKind;
import io.inverno.mod.web.compiler.spi.WebRouteInfo;
import io.inverno.mod.web.compiler.spi.WebRouteQualifiedName;
import io.inverno.mod.web.compiler.spi.WebSocketBoundPublisherInfo;
import io.inverno.mod.web.compiler.spi.WebSocketExchangeParameterInfo;
import io.inverno.mod.web.compiler.spi.WebSocketInboundParameterInfo;
import io.inverno.mod.web.compiler.spi.WebSocketInboundPublisherParameterInfo;
import io.inverno.mod.web.compiler.spi.WebSocketOutboundParameterInfo;
import io.inverno.mod.web.compiler.spi.WebSseEventFactoryParameterInfo;
import io.inverno.mod.web.compiler.spi.WebSseEventFactoryParameterInfo.SseEventFactoryKind;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 

* A factory is used to create web route info. *

* * @author Jeremy Kuhn * @since 1.0 * * @see WebRouteInfo */ class WebRouteInfoFactory { private final PluginContext pluginContext; private final PluginExecution pluginExecution; private final WebParameterInfoFactory parameterFactory; private final Map routes; private final TypeHierarchyExtractor typeHierarchyExtractor; /* Web annotations */ private final TypeMirror webControllerAnnotationType; private final TypeMirror webRoutesAnnotationType; private final TypeMirror webRouteAnnotationType; private final TypeMirror webSocketRouteAnnotationType; /* Types */ private final TypeMirror publisherType; private final TypeMirror monoType; private final TypeMirror fluxType; private final TypeMirror voidType; private final TypeMirror byteBufType; private final TypeMirror charSequenceType; private final TypeMirror sseRawEventType; private final TypeMirror sseEncoderEventType; private final TypeMirror resourceType; /** *

* Creates a web route info factory. *

* * @param pluginContext the web compiler plugin context * @param pluginExecution the web compiler plugin execution */ public WebRouteInfoFactory(PluginContext pluginContext, PluginExecution pluginExecution) { this.pluginContext = Objects.requireNonNull(pluginContext); this.pluginExecution = Objects.requireNonNull(pluginExecution); this.parameterFactory = new WebParameterInfoFactory(this.pluginContext , this.pluginExecution); this.routes = new HashMap<>(); this.typeHierarchyExtractor = new TypeHierarchyExtractor(this.pluginContext.getTypeUtils()); this.webControllerAnnotationType = this.pluginContext.getElementUtils().getTypeElement(WebController.class.getCanonicalName()).asType(); this.webRoutesAnnotationType = this.pluginContext.getElementUtils().getTypeElement(WebRoutes.class.getCanonicalName()).asType(); this.webRouteAnnotationType = this.pluginContext.getElementUtils().getTypeElement(WebRoute.class.getCanonicalName()).asType(); this.webSocketRouteAnnotationType = this.pluginContext.getElementUtils().getTypeElement(WebSocketRoute.class.getCanonicalName()).asType(); this.publisherType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(Publisher.class.getCanonicalName()).asType()); this.monoType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(Mono.class.getCanonicalName()).asType()); this.fluxType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(Flux.class.getCanonicalName()).asType()); this.voidType = this.pluginContext.getElementUtils().getTypeElement(Void.class.getCanonicalName()).asType(); this.byteBufType = this.pluginContext.getElementUtils().getTypeElement(ByteBuf.class.getCanonicalName()).asType(); this.charSequenceType = this.pluginContext.getElementUtils().getTypeElement(CharSequence.class.getCanonicalName()).asType(); this.sseRawEventType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(ResponseBody.Sse.Event.class.getCanonicalName()).asType()); this.sseEncoderEventType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(WebResponseBody.SseEncoder.Event.class.getCanonicalName()).asType()); this.resourceType = this.pluginContext.getElementUtils().getTypeElement(Resource.class.getCanonicalName()).asType(); } /** *

* Compiles the specified executable element to create a web route info. *

* * @param routeElement the executable element to compile * * @return an optional returning a web route info or an empty optional if the specified element doesn't designate a web route */ public Optional compileRoute(ExecutableElement routeElement) { AnnotationMirror webRouteAnnotation = null; AnnotationMirror webSocketRouteAnnotation = null; for(AnnotationMirror annotation : this.pluginContext.getElementUtils().getAllAnnotationMirrors(routeElement)) { if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webRouteAnnotationType)) { webRouteAnnotation = annotation; } else if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webSocketRouteAnnotationType)) { webSocketRouteAnnotation = annotation; } } if(webRouteAnnotation != null) { if(webSocketRouteAnnotation != null) { if(!this.routes.containsKey(routeElement)) { // We don't want to report error multiple times on the same element this.pluginExecution.getReporter(routeElement, webSocketRouteAnnotation).error("Can't specify both WebRoute and WebSocketRoute"); this.routes.put(routeElement, null); } // end route compilation here return Optional.empty(); } GenericWebRouteInfo routeInfo = this.routes.get(routeElement); if(routeInfo != null) { return Optional.of(routeInfo); } else { return Optional.ofNullable(this.createRoute(webRouteAnnotation, null, routeElement, routeElement, (ExecutableType)routeElement.asType(), null)); } } else if(webSocketRouteAnnotation != null) { GenericWebRouteInfo routeInfo = this.routes.get(routeElement); if(routeInfo != null) { return Optional.of(routeInfo); } else { return Optional.ofNullable(this.createWebSocketRoute(webSocketRouteAnnotation, null, routeElement, routeElement, (ExecutableType)routeElement.asType(), null)); } } else { return Optional.empty(); } } /** *

* Compiles the specified provided web router configurer bean and extracts the web routes it defines. *

* * @param bean a provided web router configurer bean * * @return the list of web routes defined in the web router configurer * * @throws IllegalArgumentException if the specified bean is not a provided web router configurer */ @SuppressWarnings("unchecked") public List compileRouterRoutes(BeanInfo bean) throws IllegalArgumentException { TypeElement beanElement = (TypeElement) this.pluginContext.getTypeUtils().asElement(bean.getType()); AnnotationMirror webRoutesAnnotation = this.pluginContext.getElementUtils().getAllAnnotationMirrors(beanElement).stream() .filter(annotation -> this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webRoutesAnnotationType)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("bean " + bean + " is not annotated with " + WebRoutes.class)); if(webRoutesAnnotation.getElementValues().isEmpty()) { return List.of(); } final AtomicInteger routeIndex = new AtomicInteger(); return this.pluginContext.getElementUtils().getElementValuesWithDefaults(webRoutesAnnotation).entrySet().stream() .flatMap(webRoutesEntry -> { switch(webRoutesEntry.getKey().getSimpleName().toString()) { case "value": { return ((Collection)webRoutesEntry.getValue().getValue()).stream() .map(value -> (AnnotationMirror)value.getValue()) .map(webRouteAnnotation -> { Set paths = new HashSet<>(); boolean matchTrailingSlash = false; Set methods = new HashSet<>(); Set consumes = new HashSet<>(); Set produces = new HashSet<>(); Set languages = new HashSet<>(); for(Entry value : this.pluginContext.getElementUtils().getElementValuesWithDefaults(webRouteAnnotation).entrySet()) { switch(value.getKey().getSimpleName().toString()) { case "path" : paths.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "matchTrailingSlash": matchTrailingSlash = (boolean)value.getValue().getValue(); break; case "method" : methods.addAll(((List)value.getValue().getValue()).stream().map(v -> Method.valueOf(v.getValue().toString())).collect(Collectors.toSet())); break; case "consumes" : consumes.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "produces" : produces.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "language" : languages.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; } } WebRouteQualifiedName routeQName = new WebRouteQualifiedName(bean.getQualifiedName(), "route_" + routeIndex.getAndIncrement()); return new ProvidedWebRouteInfo(routeQName, bean, paths, matchTrailingSlash, methods, consumes, produces, languages); }); } case "webSockets": { return ((Collection)webRoutesEntry.getValue().getValue()).stream() .map(value -> (AnnotationMirror)value.getValue()) .map(webSocketRouteAnnotation -> { Set paths = new HashSet<>(); boolean matchTrailingSlash = false; Set languages = new HashSet<>(); Set subprotocols = new HashSet<>(); WebSocketMessage.Kind messageType = WebSocketMessage.Kind.TEXT; for(Entry value : this.pluginContext.getElementUtils().getElementValuesWithDefaults(webSocketRouteAnnotation).entrySet()) { switch(value.getKey().getSimpleName().toString()) { case "path" : paths.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "matchTrailingSlash": matchTrailingSlash = (boolean)value.getValue().getValue(); break; case "language" : languages.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "subprotocol" : subprotocols.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "messageType" : messageType = WebSocketMessage.Kind.valueOf(((AnnotationValue)value.getValue().getValue()).getValue().toString()); break; } } WebRouteQualifiedName routeQName = new WebRouteQualifiedName(bean.getQualifiedName(), "route_" + routeIndex.getAndIncrement()); return new ProvidedWebSocketRouteInfo(routeQName, bean, paths, matchTrailingSlash, languages, subprotocols, messageType); }); } } return null; }) .collect(Collectors.toList()); } /** *

* Compiles the specified web controller bean and extracts the web routes it defines. *

* * @param bean a web controller bean * * @return the list of web routes defined in the web controller * * @throws IllegalArgumentException if the specified bean is not a web controller */ public List compileControllerRoutes(BeanInfo bean) throws IllegalArgumentException { TypeElement beanElement = (TypeElement) this.pluginContext.getTypeUtils().asElement(bean.getType()); this.typeHierarchyExtractor.extractTypeHierarchy(beanElement).stream() .map(element -> this.pluginContext.getElementUtils().getAllAnnotationMirrors(element).stream() .filter(annotation -> this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webControllerAnnotationType)) .findFirst() ) .filter(Optional::isPresent) .map(Optional::get) .findFirst() .orElseThrow(() -> new IllegalArgumentException("bean " + bean + " is not annotated with " + WebController.class)); Map> routesByMethodName = new HashMap<>(); for(ExecutableElement routeElement : ElementFilter.methodsIn(this.pluginContext.getElementUtils().getAllMembers(beanElement))) { this.getWebRouteAnnotatedElement(routeElement, beanElement) .ifPresent(routeAnnotatedElement -> { AnnotationMirror webRouteAnnotation = null; AnnotationMirror webSocketRouteAnnotation = null; for(AnnotationMirror annotation : this.pluginContext.getElementUtils().getAllAnnotationMirrors(routeAnnotatedElement)) { if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webRouteAnnotationType)) { webRouteAnnotation = annotation; } else if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webSocketRouteAnnotationType)) { webSocketRouteAnnotation = annotation; } } ExecutableType routeElementType = (ExecutableType)this.pluginContext.getTypeUtils().asMemberOf((DeclaredType)bean.getType(), routeElement); // We have to create the route no matter what since annotations and/or types // might be different from what the compiler found when analyzing WebRoute // annotated elements. // eg. we can imagine an interface A external to the module exposing a WebRoute // annotated method, and a class B in the module overriding that particular // interface and implementing that particular method overriding the WebRoute // annotation. If a class C extends B and implements A, then we'll found the // route coming from B in the list of routes but we'll have to consider WebRoute // annotation from A and therefore we have to recreate the route for that // particular configuration but we must not report errors already reported on B. // That being said, overriding WebRoute annotation is a bad idea, it would be // interesting to see how JAX-RS and Spring handle this. String routeMethodName = routeElement.getSimpleName().toString(); List currentRoutes = routesByMethodName.get(routeMethodName); if(currentRoutes == null) { currentRoutes = new LinkedList<>(); routesByMethodName.put(routeMethodName, currentRoutes); } // getWebRouteAnnotatedElement() makes sure we have one of webRouteAnnotation or webSocketRouteAnnotation if(webRouteAnnotation != null) { currentRoutes.add(this.createRoute(webRouteAnnotation, bean.getQualifiedName(), routeElement, routeAnnotatedElement, routeElementType, currentRoutes.isEmpty() ? null : currentRoutes.size())); } else if(webSocketRouteAnnotation != null) { currentRoutes.add(this.createWebSocketRoute(webSocketRouteAnnotation, bean.getQualifiedName(), routeElement, routeAnnotatedElement, routeElementType, currentRoutes.isEmpty() ? null : currentRoutes.size())); } }); } return routesByMethodName.values().stream().flatMap(List::stream).collect(Collectors.toList()); } @SuppressWarnings("unchecked") private GenericWebRouteInfo createRoute(AnnotationMirror webRouteAnnotation, BeanQualifiedName controllerQName, ExecutableElement routeElement, ExecutableElement routeAnnotatedElement, ExecutableType routeType, Integer discriminator) { ReporterInfo routeReporter; if(this.routes.containsKey(routeElement)) { // We don't want to report error multiple times on the same element routeReporter = new NoOpReporterInfo(this.routes.get(routeElement)); } else if(routeElement == routeAnnotatedElement) { routeReporter = this.pluginExecution.getReporter(routeElement, webRouteAnnotation); } else { routeReporter = this.pluginExecution.getReporter(routeElement); } // Get route metadata Set paths = new HashSet<>(); boolean matchTrailingSlash = false; Set methods = new HashSet<>(); Set consumes = new HashSet<>(); Set produces = new HashSet<>(); Set languages = new HashSet<>(); annotationLoop: for(Entry value : this.pluginContext.getElementUtils().getElementValuesWithDefaults(webRouteAnnotation).entrySet()) { switch(value.getKey().getSimpleName().toString()) { case "value" : paths.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break annotationLoop; case "path" : paths.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "matchTrailingSlash": matchTrailingSlash = (boolean)value.getValue().getValue(); break; case "method" : methods.addAll(((List)value.getValue().getValue()).stream().map(v -> Method.valueOf(v.getValue().toString())).collect(Collectors.toSet())); break; case "consumes" : consumes.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "produces" : produces.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "language" : languages.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; } } String routeName = routeElement.getSimpleName().toString(); if(discriminator != null) { routeName += "_" + discriminator; } WebRouteQualifiedName routeQName; if(controllerQName != null) { routeQName = new WebRouteQualifiedName(controllerQName, routeName); } else { routeQName = new WebRouteQualifiedName(routeName); } // Get Response body info TypeMirror responseBodyType = routeElement.getReturnType(); TypeMirror erasedResponseBodyType = this.pluginContext.getTypeUtils().erasure(responseBodyType); ResponseBodyReactiveKind responseBodyReactiveKind = null; if(this.pluginContext.getTypeUtils().isSameType(erasedResponseBodyType, this.publisherType)) { responseBodyReactiveKind = ResponseBodyReactiveKind.PUBLISHER; } else if(this.pluginContext.getTypeUtils().isSameType(erasedResponseBodyType, this.monoType)) { responseBodyReactiveKind = ResponseBodyReactiveKind.ONE; } else if(this.pluginContext.getTypeUtils().isSameType(erasedResponseBodyType, this.fluxType)) { responseBodyReactiveKind = ResponseBodyReactiveKind.MANY; } else { responseBodyReactiveKind = ResponseBodyReactiveKind.NONE; } ResponseBodyKind responseBodyKind = ResponseBodyKind.ENCODED; if(responseBodyReactiveKind != ResponseBodyReactiveKind.NONE) { responseBodyType = ((DeclaredType)responseBodyType).getTypeArguments().get(0); if(this.pluginContext.getTypeUtils().isSameType(responseBodyType, this.voidType)) { responseBodyKind = ResponseBodyKind.EMPTY; } else if(this.pluginContext.getTypeUtils().isSameType(responseBodyType, this.byteBufType)) { responseBodyKind = ResponseBodyKind.RAW; } else if(this.pluginContext.getTypeUtils().isAssignable(responseBodyType, this.charSequenceType)) { if(produces.isEmpty()) { responseBodyKind = ResponseBodyKind.CHARSEQUENCE; } } else if(this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(responseBodyType), this.sseRawEventType)) { responseBodyType = ((DeclaredType)responseBodyType).getTypeArguments().get(0); if(this.pluginContext.getTypeUtils().isSameType(responseBodyType, this.byteBufType)) { responseBodyKind = ResponseBodyKind.SSE_RAW; } else if(this.pluginContext.getTypeUtils().isAssignable(responseBodyType, this.charSequenceType)) { responseBodyKind = ResponseBodyKind.SSE_CHARSEQUENCE; } else { routeReporter.error("Unsupported server-sent event type: " + responseBodyType); } } else if(this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(responseBodyType), this.sseEncoderEventType)) { responseBodyKind = ResponseBodyKind.SSE_ENCODED; responseBodyType = ((DeclaredType)responseBodyType).getTypeArguments().get(0); } } else if(responseBodyType.getKind() == TypeKind.VOID) { responseBodyKind = ResponseBodyKind.EMPTY; } else if(this.pluginContext.getTypeUtils().isSameType(responseBodyType, this.byteBufType)) { responseBodyKind = ResponseBodyKind.RAW; } else if(this.pluginContext.getTypeUtils().isAssignable(responseBodyType, this.charSequenceType)) { if(produces.isEmpty()) { responseBodyKind = ResponseBodyKind.CHARSEQUENCE; } } else if(this.pluginContext.getTypeUtils().isSameType(responseBodyType, this.resourceType)) { responseBodyKind = ResponseBodyKind.RESOURCE; } WebResponseBodyInfo responseBodyInfo = new GenericWebResponseBodyInfo(responseBodyType, responseBodyKind, responseBodyReactiveKind); // Get route parameters List parameters = new ArrayList<>(); boolean hasFormParameters = false; boolean hasExchangeParameter = false; List bodyParameters = new ArrayList<>(); WebSseEventFactoryParameterInfo sseFactoryParameter = null; List routeParameters = routeElement.getParameters(); List routeAnnotatedParameters = routeAnnotatedElement.getParameters(); List routeParameterTypes = routeType.getParameterTypes(); for(int i=0;i 0) { routeReporter.error("Can't mix Body and Form parameters"); } if(bodyParameters.size() > 1) { routeReporter.error("Multiple Body parameters"); } if(sseFactoryParameter != null) { if(responseBodyInfo.getBodyKind() == ResponseBodyKind.SSE_RAW) { if(sseFactoryParameter.getEventFactoryKind() != SseEventFactoryKind.RAW) { routeReporter.error("SSE event factory " + sseFactoryParameter.getType() + " doesn't match sse response " + responseBodyInfo.getType()); } } else if(responseBodyInfo.getBodyKind() == ResponseBodyKind.SSE_CHARSEQUENCE) { if(sseFactoryParameter.getEventFactoryKind() != SseEventFactoryKind.CHARSEQUENCE) { routeReporter.error("SSE event factory " + sseFactoryParameter.getType() + " doesn't match sse response " + responseBodyInfo.getType()); } } else if(responseBodyInfo.getBodyKind() == ResponseBodyKind.SSE_ENCODED) { if(sseFactoryParameter.getEventFactoryKind() != SseEventFactoryKind.ENCODED || !this.pluginContext.getTypeUtils().isSameType(responseBodyInfo.getType(), sseFactoryParameter.getType())) { routeReporter.error("SSE event factory " + sseFactoryParameter.getType() + " doesn't match sse response " + responseBodyInfo.getType()); } } else { routeReporter.error("Invalid SSE route declaration which must return a publisher of SSE events and define a corresponding SSE event factory parameter"); } } else if(responseBodyInfo.getBodyKind() == ResponseBodyKind.SSE_RAW || responseBodyInfo.getBodyKind() == ResponseBodyKind.SSE_ENCODED) { routeReporter.error("Invalid SSE route declaration which must return a publisher of SSE events and define a corresponding SSE event factory parameter"); } GenericWebRouteInfo routeInfo = new GenericWebRouteInfo(routeElement, routeQName, routeReporter, paths, matchTrailingSlash, methods, consumes, produces, languages, parameters, responseBodyInfo); this.routes.putIfAbsent(routeElement, routeInfo); return routeInfo; } @SuppressWarnings("unchecked") private GenericWebSocketRouteInfo createWebSocketRoute(AnnotationMirror webSocketRouteAnnotation, BeanQualifiedName controllerQName, ExecutableElement routeElement, ExecutableElement routeAnnotatedElement, ExecutableType routeType, Integer discriminator) { ReporterInfo routeReporter; if(this.routes.containsKey(routeElement)) { // We don't want to report error multiple times on the same element routeReporter = new NoOpReporterInfo(this.routes.get(routeElement)); } else if(routeElement == routeAnnotatedElement) { routeReporter = this.pluginExecution.getReporter(routeElement, webSocketRouteAnnotation); } else { routeReporter = this.pluginExecution.getReporter(routeElement); } // Get route metadata Set paths = new HashSet<>(); boolean matchTrailingSlash = false; Set languages = new HashSet<>(); Set subprotocols = new HashSet<>(); WebSocketMessage.Kind messageType = WebSocketMessage.Kind.TEXT; for(Entry value : this.pluginContext.getElementUtils().getElementValuesWithDefaults(webSocketRouteAnnotation).entrySet()) { switch(value.getKey().getSimpleName().toString()) { case "path" : paths.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "matchTrailingSlash": matchTrailingSlash = (boolean)value.getValue().getValue(); break; case "language" : languages.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "subprotocol" : subprotocols.addAll(((List)value.getValue().getValue()).stream().map(v -> v.getValue().toString()).collect(Collectors.toSet())); break; case "messageType" : messageType = WebSocketMessage.Kind.valueOf(((AnnotationValue)value.getValue()).getValue().toString()); break; } } String routeName = routeElement.getSimpleName().toString(); if(discriminator != null) { routeName += "_" + discriminator; } WebRouteQualifiedName routeQName; if(controllerQName != null) { routeQName = new WebRouteQualifiedName(controllerQName, routeName); } else { routeQName = new WebRouteQualifiedName(routeName); } // Determine whether we have an outbound publisher as return type GenericWebSocketOutboundPublisherInfo outboundPublisherInfo = null; WebSocketBoundPublisherInfo.BoundReactiveKind outboundReactiveKind = null; TypeMirror returnType = routeElement.getReturnType(); if(returnType.getKind() != TypeKind.VOID) { TypeMirror erasedReturnType = this.pluginContext.getTypeUtils().erasure(returnType); if(this.pluginContext.getTypeUtils().isSameType(erasedReturnType, this.publisherType)) { outboundReactiveKind = WebSocketBoundPublisherInfo.BoundReactiveKind.PUBLISHER; } else if(this.pluginContext.getTypeUtils().isSameType(erasedReturnType, this.monoType)) { outboundReactiveKind = WebSocketBoundPublisherInfo.BoundReactiveKind.ONE; } else if(this.pluginContext.getTypeUtils().isSameType(erasedReturnType, this.fluxType)) { outboundReactiveKind = WebSocketBoundPublisherInfo.BoundReactiveKind.MANY; } else { // This is not allowed routeReporter.error("Must return void or an outbound publisher"); } } if(outboundReactiveKind != null) { // We have an outbound publisher as return type WebSocketBoundPublisherInfo.BoundKind outboundKind = WebSocketBoundPublisherInfo.BoundKind.ENCODED; TypeMirror boundType = ((DeclaredType)returnType).getTypeArguments().get(0); if(this.pluginContext.getTypeUtils().isSameType(boundType, this.voidType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.EMPTY; } else if(this.pluginContext.getTypeUtils().isSameType(boundType, this.byteBufType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.RAW_REDUCED; } else if(this.pluginContext.getTypeUtils().isAssignable(boundType, this.charSequenceType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.CHARSEQUENCE_REDUCED; } else if(boundType instanceof DeclaredType && ((DeclaredType)boundType).getTypeArguments().size() == 1) { // maybe we have a reactive message payload TypeMirror erasedBoundType = this.pluginContext.getTypeUtils().erasure(boundType); TypeMirror nextBoundType = ((DeclaredType)boundType).getTypeArguments().get(0); if(this.pluginContext.getTypeUtils().isSameType(erasedBoundType, this.publisherType)) { boundType = nextBoundType; if(this.pluginContext.getTypeUtils().isSameType(nextBoundType, this.byteBufType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.RAW_PUBLISHER; } else if(this.pluginContext.getTypeUtils().isAssignable(nextBoundType, this.charSequenceType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.CHARSEQUENCE_PUBLISHER; } } else if(this.pluginContext.getTypeUtils().isSameType(erasedBoundType, this.fluxType)) { boundType = nextBoundType; if(this.pluginContext.getTypeUtils().isSameType(nextBoundType, this.byteBufType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.RAW_MANY; } else if(this.pluginContext.getTypeUtils().isAssignable(nextBoundType, this.charSequenceType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.CHARSEQUENCE_MANY; } } else if(this.pluginContext.getTypeUtils().isSameType(erasedBoundType, this.monoType)) { boundType = nextBoundType; if(this.pluginContext.getTypeUtils().isSameType(nextBoundType, this.byteBufType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.RAW_REDUCED_ONE; } else if(this.pluginContext.getTypeUtils().isAssignable(nextBoundType, this.charSequenceType)) { outboundKind = WebSocketBoundPublisherInfo.BoundKind.CHARSEQUENCE_REDUCED_ONE; } } } outboundPublisherInfo = new GenericWebSocketOutboundPublisherInfo(boundType, outboundKind, outboundReactiveKind); } // Get route parameters List parameters = new ArrayList<>(); boolean hasExchangeParameter = false; boolean hasInboundParameter = false; boolean hasOutboundParameter = false; List routeParameters = routeElement.getParameters(); List routeAnnotatedParameters = routeAnnotatedElement.getParameters(); List routeParameterTypes = routeType.getParameterTypes(); for(int i=0;i getWebRouteAnnotatedElement(ExecutableElement executableElement, TypeElement typeElement) { List overriddenElements = this.typeHierarchyExtractor.extractTypeHierarchy(typeElement).stream() .map(element -> ElementFilter.methodsIn(element.getEnclosedElements()).stream() .filter(methodElement -> methodElement.equals(executableElement) || this.pluginContext.getElementUtils().overrides(executableElement, methodElement, typeElement)) .findFirst() ) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); // We must make sure we have either @WebRoute or @WebSocketRoute but not both defined anywhere in the chain Optional webRouteAnnotatedElement = overriddenElements.stream().filter(overriddenElement -> overriddenElement.getAnnotationMirrors().stream().anyMatch(annotation -> this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webRouteAnnotationType))).findFirst(); if(webRouteAnnotatedElement.isPresent()) { if(overriddenElements.stream().anyMatch(overriddenElement -> overriddenElement.getAnnotationMirrors().stream().anyMatch(annotation -> this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webSocketRouteAnnotationType)))) { if(!this.routes.containsKey(webRouteAnnotatedElement.get())) { this.pluginExecution.getReporter(webRouteAnnotatedElement.get()).error("Can't specify both WebRoute and WebSocketRoute"); this.routes.put(webRouteAnnotatedElement.get(), null); } // end route compilation here return Optional.empty(); } return webRouteAnnotatedElement; } return overriddenElements.stream().filter(overriddenElement -> overriddenElement.getAnnotationMirrors().stream().anyMatch(annotation -> this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.webSocketRouteAnnotationType))).findFirst(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy