io.inverno.mod.web.compiler.internal.WebParameterInfoFactory Maven / Gradle / Ivy
Show all versions of inverno-web-compiler Show documentation
/*
* 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.ReporterInfo;
import io.inverno.core.compiler.spi.plugin.PluginContext;
import io.inverno.core.compiler.spi.plugin.PluginExecution;
import io.inverno.mod.http.base.ExchangeContext;
import io.inverno.mod.http.base.Parameter;
import io.inverno.mod.http.server.ResponseBody;
import io.inverno.mod.web.server.Web2SocketExchange;
import io.inverno.mod.web.server.WebExchange;
import io.inverno.mod.web.server.WebPart;
import io.inverno.mod.web.server.WebResponseBody;
import io.inverno.mod.web.server.annotation.Body;
import io.inverno.mod.web.server.annotation.CookieParam;
import io.inverno.mod.web.server.annotation.FormParam;
import io.inverno.mod.web.server.annotation.HeaderParam;
import io.inverno.mod.web.server.annotation.PathParam;
import io.inverno.mod.web.server.annotation.QueryParam;
import io.inverno.mod.web.server.annotation.SseEventFactory;
import io.inverno.mod.web.server.annotation.WebRoute;
import io.inverno.mod.web.compiler.spi.WebParameterInfo;
import io.inverno.mod.web.compiler.spi.WebParameterQualifiedName;
import io.inverno.mod.web.compiler.spi.WebRequestBodyParameterInfo.RequestBodyKind;
import io.inverno.mod.web.compiler.spi.WebRequestBodyParameterInfo.RequestBodyReactiveKind;
import io.inverno.mod.web.compiler.spi.WebRouteQualifiedName;
import io.inverno.mod.web.compiler.spi.WebSocketBoundPublisherInfo;
import io.inverno.mod.web.compiler.spi.WebSseEventFactoryParameterInfo.SseEventFactoryKind;
import io.netty.buffer.ByteBuf;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
*
* A web parameter info factory is used to create web parameter info.
*
*
* @author Jeremy Kuhn
* @since 1.0
*
* @see WebParameterInfo
*/
class WebParameterInfoFactory {
private final PluginContext pluginContext;
private final PluginExecution pluginExecution;
private final Map processedParameterElements;
/* Web annotations */
private final TypeMirror bodyAnnotationType;
private final TypeMirror cookieParameterAnnotationType;
private final TypeMirror formParameterAnnotationType;
private final TypeMirror headerParameterAnnotationType;
private final TypeMirror pathParameterAnnotationType;
private final TypeMirror queryParameterAnnotationType;
private final TypeMirror sseEventFactoryAnnotationType;
/* Contextual */
private final TypeMirror webExchangeType;
private final TypeMirror exchangeContextType;
private final TypeMirror sseEventFactoryType;
private final TypeMirror sseEncoderEventFactoryType;
private final TypeMirror web2SocketExchangeType;
private final TypeMirror web2SocketExchangeInboundType;
private final TypeMirror web2SocketExchangeOutboundType;
/* Types */
private final TypeMirror optionalType;
private final TypeMirror monoType;
private final TypeMirror fluxType;
private final TypeMirror voidType;
private final TypeMirror publisherType;
private final TypeMirror byteBufType;
private final TypeMirror charSequenceType;
private final TypeMirror webPartType;
private final TypeMirror parameterType;
/**
*
* Creates a web parameter info factory.
*
*
* @param pluginContext the web compiler plugin context
* @param pluginExecution the web compiler plugin execution
*/
public WebParameterInfoFactory(PluginContext pluginContext, PluginExecution pluginExecution) {
this.pluginContext = Objects.requireNonNull(pluginContext);
this.pluginExecution = Objects.requireNonNull(pluginExecution);
this.processedParameterElements = new HashMap<>();
this.bodyAnnotationType = this.pluginContext.getElementUtils().getTypeElement(Body.class.getCanonicalName()).asType();
this.cookieParameterAnnotationType = this.pluginContext.getElementUtils().getTypeElement(CookieParam.class.getCanonicalName()).asType();
this.formParameterAnnotationType = this.pluginContext.getElementUtils().getTypeElement(FormParam.class.getCanonicalName()).asType();
this.headerParameterAnnotationType = this.pluginContext.getElementUtils().getTypeElement(HeaderParam.class.getCanonicalName()).asType();
this.pathParameterAnnotationType = this.pluginContext.getElementUtils().getTypeElement(PathParam.class.getCanonicalName()).asType();
this.queryParameterAnnotationType = this.pluginContext.getElementUtils().getTypeElement(QueryParam.class.getCanonicalName()).asType();
this.sseEventFactoryAnnotationType = this.pluginContext.getElementUtils().getTypeElement(SseEventFactory.class.getCanonicalName()).asType();
this.optionalType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(Optional.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.publisherType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(Publisher.class.getCanonicalName()).asType());
this.byteBufType = this.pluginContext.getElementUtils().getTypeElement(ByteBuf.class.getCanonicalName()).asType();
this.charSequenceType = this.pluginContext.getElementUtils().getTypeElement(CharSequence.class.getCanonicalName()).asType();
this.webPartType = this.pluginContext.getElementUtils().getTypeElement(WebPart.class.getCanonicalName()).asType();
this.parameterType = this.pluginContext.getElementUtils().getTypeElement(Parameter.class.getCanonicalName()).asType();
this.webExchangeType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(WebExchange.class.getCanonicalName()).asType());
this.exchangeContextType = this.pluginContext.getElementUtils().getTypeElement(ExchangeContext.class.getCanonicalName()).asType();
this.sseEventFactoryType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(ResponseBody.Sse.EventFactory.class.getCanonicalName()).asType());
this.sseEncoderEventFactoryType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(WebResponseBody.SseEncoder.EventFactory.class.getCanonicalName()).asType());
this.web2SocketExchangeType = this.pluginContext.getTypeUtils().erasure(this.pluginContext.getElementUtils().getTypeElement(Web2SocketExchange.class.getCanonicalName()).asType());
this.web2SocketExchangeInboundType = this.pluginContext.getElementUtils().getTypeElement(Web2SocketExchange.Inbound.class.getCanonicalName()).asType();
this.web2SocketExchangeOutboundType = this.pluginContext.getElementUtils().getTypeElement(Web2SocketExchange.Outbound.class.getCanonicalName()).asType();
}
/**
*
* Creates a web parameter from a parameter element.
*
*
* @param routeQName the qualified name of the route for which the parameter is defined
* @param parameterElement the variable element of the parameter
* @param annotatedParameterElement the variable element of the parameter in the method actually annotated with {@link WebRoute @WebRoute} annotation
* @param parameterType the parameter type
*
* @return an abstract web parameter info
*/
public AbstractWebParameterInfo createParameter(WebRouteQualifiedName routeQName, VariableElement parameterElement, VariableElement annotatedParameterElement, TypeMirror parameterType) {
AbstractWebParameterInfo result = null;
WebParameterQualifiedName parameterQName = new WebParameterQualifiedName(routeQName, annotatedParameterElement.getSimpleName().toString());
boolean required = !this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(parameterType), this.optionalType);
if(!required) {
// For optional parameter consider the Optional<> argument
parameterType = ((DeclaredType)parameterType).getTypeArguments().get(0);
}
ReporterInfo parameterReporter;
if(this.processedParameterElements.containsKey(parameterElement)) {
parameterReporter = new NoOpReporterInfo(this.processedParameterElements.get(parameterElement));
}
else {
parameterReporter = this.pluginExecution.getReporter(parameterElement);
}
// A web parameter can't be annotated with multiple web parameter annotations
for(AnnotationMirror annotation : annotatedParameterElement.getAnnotationMirrors()) {
AbstractWebParameterInfo currentParameterInfo = null;
if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.bodyAnnotationType)) {
if(!required) {
parameterReporter.error("Request body parameter can't be optional");
}
else {
currentParameterInfo = this.createRequestBodyParameter(parameterReporter, parameterQName, parameterElement, parameterType);
}
}
else if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.cookieParameterAnnotationType)) {
currentParameterInfo = this.createCookieParameter(parameterReporter, parameterQName, parameterElement, parameterType, required);
}
else if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.formParameterAnnotationType)) {
currentParameterInfo = this.createFormParameter(parameterReporter, parameterQName, parameterElement, parameterType, required);
}
else if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.headerParameterAnnotationType)) {
currentParameterInfo = this.createHeaderParameter(parameterReporter, parameterQName, parameterElement, parameterType, required);
}
else if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.pathParameterAnnotationType)) {
currentParameterInfo = this.createPathParameter(parameterReporter, parameterQName, parameterElement, parameterType, required);
}
else if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.queryParameterAnnotationType)) {
currentParameterInfo = this.createQueryParameter(parameterReporter, parameterQName, parameterElement, parameterType, required);
}
else if(this.pluginContext.getTypeUtils().isSameType(annotation.getAnnotationType(), this.sseEventFactoryAnnotationType)) {
if(!required) {
parameterReporter.error("SSE event factory parameter can't be optional");
}
else {
currentParameterInfo = this.createSseEventFactoryParameter(parameterReporter, parameterQName, parameterElement);
}
}
if(currentParameterInfo != null) {
if(result != null) {
parameterReporter.error("Too many web parameter annotations specified, only one is allowed");
break;
}
result = currentParameterInfo;
}
}
// Contextual
if(result == null) {
if(this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(parameterElement.asType()), this.webExchangeType)) {
result = this.createExchangeParameter(parameterReporter, parameterQName, parameterElement);
}
else if(this.pluginContext.getTypeUtils().isAssignable(parameterElement.asType(), this.exchangeContextType)) {
result = this.createExchangeContextParameter(parameterReporter, parameterQName, parameterElement);
}
}
if(result == null) {
if(!parameterReporter.hasError()) {
parameterReporter.error("Invalid parameter which is neither a web parameter, nor a valid contextual parameter");
}
result = new InvalidWebParameterInfo(parameterQName, parameterReporter, parameterElement, required);
}
this.processedParameterElements.putIfAbsent(parameterElement, result);
return result;
}
/**
*
* Creates a WebSocket parameter from a parameter element.
*
*
* @param routeQName the qualified name of the route for which the parameter is defined
* @param parameterElement the variable element of the parameter
* @param annotatedParameterElement the variable element of the parameter in the method actually annotated with {@link WebRoute @WebRoute} annotation
* @param parameterType the parameter type
*
* @return an abstract web parameter info
*/
public AbstractWebParameterInfo createWebSocketParameter(WebRouteQualifiedName routeQName, VariableElement parameterElement, VariableElement annotatedParameterElement, TypeMirror parameterType) {
AbstractWebParameterInfo result;
WebParameterQualifiedName parameterQName = new WebParameterQualifiedName(routeQName, annotatedParameterElement.getSimpleName().toString());
ReporterInfo parameterReporter;
if(this.processedParameterElements.containsKey(parameterElement)) {
parameterReporter = new NoOpReporterInfo(this.processedParameterElements.get(parameterElement));
}
else {
parameterReporter = this.pluginExecution.getReporter(parameterElement);
}
// only contextual, no annotation:
// - Web2SocketExchange.Inbound
// - Web2SocketExchange.Outbound
// - Web2SockerExchange extends Context>
// - Publiser, Flux, Mono
// - T extends Context
// Contextual
if(this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(parameterElement.asType()), this.web2SocketExchangeType)) {
result = this.createWebSocketExchangeParameter(parameterReporter, parameterQName, parameterElement);
}
else if(this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(parameterElement.asType()), this.web2SocketExchangeInboundType)) {
result = this.createWebSocketInboundParameter(parameterReporter, parameterQName, parameterElement);
}
else if(this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(parameterElement.asType()), this.web2SocketExchangeOutboundType)) {
result = this.createWebSocketOutboundParameter(parameterReporter, parameterQName, parameterElement);
}
else if(this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(parameterElement.asType()), this.publisherType)
|| this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(parameterElement.asType()), this.fluxType)
|| this.pluginContext.getTypeUtils().isSameType(this.pluginContext.getTypeUtils().erasure(parameterElement.asType()), this.monoType)) {
result = this.createWebSocketInboundPublisherParameter(parameterReporter, parameterQName, parameterElement, parameterType);
}
else if(this.pluginContext.getTypeUtils().isAssignable(parameterElement.asType(), this.exchangeContextType)) {
result = this.createExchangeContextParameter(parameterReporter, parameterQName, parameterElement);
}
else {
parameterReporter.error("Invalid parameter which is not a WebSocket parameter");
result = new InvalidWebParameterInfo(parameterQName, parameterReporter, parameterElement, false);
}
this.processedParameterElements.putIfAbsent(parameterElement, result);
return result;
}
/**
*
* Creates a cookie parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
* @param required true to indicate a required parameter, false
* otherwise
*
* @return a web cookie parameter info
*/
private GenericWebCookieParameterInfo createCookieParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement, TypeMirror parameterType, boolean required) {
return new GenericWebCookieParameterInfo(parameterQName, reporter, parameterElement, parameterType, required);
}
/**
*
* Creates an exchange parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
*
* @return a web exchange parameter info
*/
private GenericWebExchangeParameterInfo createExchangeParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement) {
TypeMirror contextType = this.exchangeContextType;
List extends TypeMirror> typeArguments = ((DeclaredType)parameterElement.asType()).getTypeArguments();
if(!typeArguments.isEmpty()) {
contextType = typeArguments.get(0);
if(contextType.getKind() == TypeKind.WILDCARD) {
TypeMirror extendsBound = ((WildcardType)contextType).getExtendsBound();
if(extendsBound != null) {
contextType = extendsBound;
}
else {
contextType = this.exchangeContextType;
}
}
else if(contextType.getKind() == TypeKind.TYPEVAR) {
contextType = ((TypeVariable)contextType).getUpperBound();
}
}
List extends TypeMirror> actualTypes;
if(contextType.getKind() == TypeKind.INTERSECTION) {
actualTypes = ((IntersectionType)contextType).getBounds();
}
else {
actualTypes = List.of(contextType);
}
if(actualTypes.stream().anyMatch(type -> this.pluginContext.getTypeUtils().asElement(type).getKind() != ElementKind.INTERFACE)) {
reporter.error("Web exchange context must be an interface");
}
return new GenericWebExchangeParameterInfo(parameterQName, reporter, parameterElement, contextType);
}
/**
*
* Creates an exchange context parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
*
* @return a web exchange context parameter info
*/
private GenericWebExchangeContextParameterInfo createExchangeContextParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement) {
TypeMirror contextType = parameterElement.asType();
if(contextType.getKind() == TypeKind.TYPEVAR) {
contextType = ((TypeVariable)contextType).getUpperBound();
}
List extends TypeMirror> actualTypes;
if(contextType.getKind() == TypeKind.INTERSECTION) {
actualTypes = ((IntersectionType)contextType).getBounds();
}
else {
actualTypes = List.of(contextType);
}
if(actualTypes.stream().anyMatch(type -> this.pluginContext.getTypeUtils().asElement(type).getKind() != ElementKind.INTERFACE)) {
reporter.error("Web exchange context must be an interface");
}
return new GenericWebExchangeContextParameterInfo(parameterQName, reporter, parameterElement, contextType);
}
/**
*
* Creates a server-sent event factory parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
*
* @return a web server-sent event factory parameter info
*/
private GenericWebSseEventFactoryParameterInfo createSseEventFactoryParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement) {
if(this.pluginContext.getTypeUtils().isSameType(this.sseEncoderEventFactoryType, this.pluginContext.getTypeUtils().erasure(parameterElement.asType()))) {
return new GenericWebSseEventFactoryParameterInfo(parameterQName, reporter, parameterElement, ((DeclaredType)parameterElement.asType()).getTypeArguments().get(0), SseEventFactoryKind.ENCODED, parameterElement.getAnnotation(SseEventFactory.class).value());
}
else if(this.pluginContext.getTypeUtils().isSameType(this.sseEventFactoryType, this.pluginContext.getTypeUtils().erasure(parameterElement.asType()))) {
TypeMirror sseEventType = ((DeclaredType)parameterElement.asType()).getTypeArguments().get(0);
SseEventFactoryKind sseFactoryType = SseEventFactoryKind.RAW;
if(this.pluginContext.getTypeUtils().isSameType(sseEventType, this.byteBufType)) {
sseFactoryType = SseEventFactoryKind.RAW;
}
else if(this.pluginContext.getTypeUtils().isAssignable(sseEventType, this.charSequenceType)) {
sseFactoryType = SseEventFactoryKind.CHARSEQUENCE;
}
else {
reporter.error("Unsupported server-sent event type: " + sseEventType);
}
return new GenericWebSseEventFactoryParameterInfo(parameterQName, reporter, parameterElement, sseEventType, sseFactoryType, parameterElement.getAnnotation(SseEventFactory.class).value());
}
else {
reporter.error("Invalid SSE event factory parameter which must be of type " + this.sseEventFactoryType + " or " + this.sseEncoderEventFactoryType);
return new GenericWebSseEventFactoryParameterInfo(parameterQName, reporter, parameterElement, this.byteBufType, SseEventFactoryKind.RAW);
}
}
/**
*
* Creates a form parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
* @param required true to indicate a required parameter, false
* otherwise
*
* @return a web form parameter info
*/
private GenericWebFormParameterInfo createFormParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement, TypeMirror parameterType, boolean required) {
return new GenericWebFormParameterInfo(parameterQName, reporter, parameterElement, parameterType, required);
}
/**
*
* Creates a header parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
* @param required true to indicate a required parameter, false otherwise
*
* @return a web header parameter info
*/
private GenericWebHeaderParameterInfo createHeaderParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement, TypeMirror parameterType, boolean required) {
return new GenericWebHeaderParameterInfo(parameterQName, reporter, parameterElement, parameterType, required);
}
/**
*
* Creates a path parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
* @param required true to indicate a required parameter, false otherwise
*
* @return a web path parameter info
*/
private GenericWebPathParameterInfo createPathParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement, TypeMirror parameterType, boolean required) {
return new GenericWebPathParameterInfo(parameterQName, reporter, parameterElement, parameterType, required);
}
/**
*
* Creates a query parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
* @param required true to indicate a required parameter, false otherwise
*
* @return a web query parameter info
*/
private GenericWebQueryParameterInfo createQueryParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement, TypeMirror parameterType, boolean required) {
return new GenericWebQueryParameterInfo(parameterQName, reporter, parameterElement, parameterType, required);
}
/**
*
* Creates a web request body parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
*
* @return a request body parameter info
*/
private GenericWebRequestBodyParameterInfo createRequestBodyParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement, TypeMirror parameterType) {
TypeMirror requestBodyType = parameterType;
TypeMirror erasedRequestBodyType = this.pluginContext.getTypeUtils().erasure(requestBodyType);
RequestBodyReactiveKind requestBodyReactiveKind = null;
if(this.pluginContext.getTypeUtils().isSameType(erasedRequestBodyType, this.publisherType)) {
requestBodyReactiveKind = RequestBodyReactiveKind.PUBLISHER;
}
else if(this.pluginContext.getTypeUtils().isSameType(erasedRequestBodyType, this.monoType)) {
requestBodyReactiveKind = RequestBodyReactiveKind.ONE;
}
else if(this.pluginContext.getTypeUtils().isSameType(erasedRequestBodyType, this.fluxType)) {
requestBodyReactiveKind = RequestBodyReactiveKind.MANY;
}
else {
requestBodyReactiveKind = RequestBodyReactiveKind.NONE;
}
RequestBodyKind requestBodyKind = RequestBodyKind.ENCODED;
if(requestBodyReactiveKind != RequestBodyReactiveKind.NONE) {
requestBodyType = ((DeclaredType)requestBodyType).getTypeArguments().get(0);
if(this.pluginContext.getTypeUtils().isSameType(requestBodyType, this.byteBufType)) {
requestBodyKind = RequestBodyKind.RAW;
}
else if(this.pluginContext.getTypeUtils().isAssignable(this.webPartType, requestBodyType)) {
requestBodyKind = RequestBodyKind.MULTIPART;
}
else if(this.pluginContext.getTypeUtils().isSameType(requestBodyType, this.parameterType)) {
requestBodyKind = RequestBodyKind.URLENCODED;
}
else if(this.pluginContext.getTypeUtils().isSameType(requestBodyType, this.voidType)) {
reporter.error("Request body publisher can't have type argument void");
}
}
else if(this.pluginContext.getTypeUtils().isSameType(requestBodyType, this.byteBufType)) {
requestBodyKind = RequestBodyKind.RAW;
}
return new GenericWebRequestBodyParameterInfo(parameterQName, reporter, parameterElement, requestBodyType, requestBodyKind, requestBodyReactiveKind);
}
/**
*
* Creates a WebSocket exchange parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
*
* @return a WebSocket exchange parameter info
*/
private GenericWebSocketExchangeParameterInfo createWebSocketExchangeParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement) {
TypeMirror contextType = this.exchangeContextType;
List extends TypeMirror> typeArguments = ((DeclaredType)parameterElement.asType()).getTypeArguments();
if(!typeArguments.isEmpty()) {
contextType = typeArguments.get(0);
if(contextType.getKind() == TypeKind.WILDCARD) {
TypeMirror extendsBound = ((WildcardType)contextType).getExtendsBound();
if(extendsBound != null) {
contextType = extendsBound;
}
else {
contextType = this.exchangeContextType;
}
}
else if(contextType.getKind() == TypeKind.TYPEVAR) {
contextType = ((TypeVariable)contextType).getUpperBound();
}
}
List extends TypeMirror> actualTypes;
if(contextType.getKind() == TypeKind.INTERSECTION) {
actualTypes = ((IntersectionType)contextType).getBounds();
}
else {
actualTypes = List.of(contextType);
}
if(actualTypes.stream().anyMatch(type -> this.pluginContext.getTypeUtils().asElement(type).getKind() != ElementKind.INTERFACE)) {
reporter.error("Web exchange context must be an interface");
}
return new GenericWebSocketExchangeParameterInfo(parameterQName, reporter, parameterElement, contextType);
}
/**
*
* Creates a WebSocket inbound parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
*
* @return a WebSocket inbound parameter info
*/
private GenericWebSocketInboundParameterInfo createWebSocketInboundParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement) {
return new GenericWebSocketInboundParameterInfo(parameterQName, reporter, parameterElement, this.web2SocketExchangeInboundType);
}
/**
*
* Creates a WebSocket inbound publisher parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
*
* @return a WebSocket inbound publisher parameter info
*/
private GenericWebSocketInboundPublisherParameterInfo createWebSocketInboundPublisherParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement, TypeMirror parameterType) {
// 1. determine reactive kind
// 2. determine kind
WebSocketBoundPublisherInfo.BoundReactiveKind inboundReactiveKind = null;
TypeMirror erasedParameterType = this.pluginContext.getTypeUtils().erasure(parameterType);
if(this.pluginContext.getTypeUtils().isSameType(erasedParameterType, this.publisherType)) {
inboundReactiveKind = WebSocketBoundPublisherInfo.BoundReactiveKind.PUBLISHER;
}
else if(this.pluginContext.getTypeUtils().isSameType(erasedParameterType, this.monoType)) {
inboundReactiveKind = WebSocketBoundPublisherInfo.BoundReactiveKind.ONE;
}
else if(this.pluginContext.getTypeUtils().isSameType(erasedParameterType, this.fluxType)) {
inboundReactiveKind = WebSocketBoundPublisherInfo.BoundReactiveKind.MANY;
}
else {
// should never happen, this is checked in createWebSocketRoute()
throw new IllegalStateException("Unexpected inbound publisher type");
}
// We have an outbound publisher as return type
WebSocketBoundPublisherInfo.BoundKind inboundKind = WebSocketBoundPublisherInfo.BoundKind.ENCODED;
TypeMirror boundType = ((DeclaredType)parameterType).getTypeArguments().get(0);
if(this.pluginContext.getTypeUtils().isSameType(boundType, this.voidType)) {
inboundKind = WebSocketBoundPublisherInfo.BoundKind.EMPTY;
}
else if(this.pluginContext.getTypeUtils().isSameType(boundType, this.byteBufType)) {
inboundKind = WebSocketBoundPublisherInfo.BoundKind.RAW_REDUCED;
}
else if(this.pluginContext.getTypeUtils().isAssignable(boundType, this.charSequenceType)) {
inboundKind = 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)) {
if(this.pluginContext.getTypeUtils().isSameType(nextBoundType, this.byteBufType)) {
inboundKind = WebSocketBoundPublisherInfo.BoundKind.RAW_PUBLISHER;
}
else if(this.pluginContext.getTypeUtils().isAssignable(nextBoundType, this.charSequenceType)) {
inboundKind = WebSocketBoundPublisherInfo.BoundKind.CHARSEQUENCE_PUBLISHER;
}
}
else if(this.pluginContext.getTypeUtils().isSameType(erasedBoundType, this.fluxType)) {
if(this.pluginContext.getTypeUtils().isSameType(nextBoundType, this.byteBufType)) {
inboundKind = WebSocketBoundPublisherInfo.BoundKind.RAW_MANY;
}
else if(this.pluginContext.getTypeUtils().isAssignable(nextBoundType, this.charSequenceType)) {
inboundKind = WebSocketBoundPublisherInfo.BoundKind.CHARSEQUENCE_MANY;
}
}
else if(this.pluginContext.getTypeUtils().isSameType(erasedBoundType, this.monoType)) {
if(this.pluginContext.getTypeUtils().isSameType(nextBoundType, this.byteBufType)) {
inboundKind = WebSocketBoundPublisherInfo.BoundKind.RAW_REDUCED_ONE;
}
else if(this.pluginContext.getTypeUtils().isAssignable(nextBoundType, this.charSequenceType)) {
inboundKind = WebSocketBoundPublisherInfo.BoundKind.CHARSEQUENCE_REDUCED_ONE;
}
}
}
return new GenericWebSocketInboundPublisherParameterInfo(parameterQName, reporter, parameterElement, boundType, inboundKind, inboundReactiveKind);
}
/**
*
* Creates a WebSocket outbound parameter info.
*
*
* @param reporter the parameter reporter
* @param parameterQName the parameter qualified name
* @param parameterElement the parameter element
* @param parameterType the parameter type
*
* @return a WebSocket outbound parameter info
*/
private GenericWebSocketOutboundParameterInfo createWebSocketOutboundParameter(ReporterInfo reporter, WebParameterQualifiedName parameterQName, VariableElement parameterElement) {
return new GenericWebSocketOutboundParameterInfo(parameterQName, reporter, parameterElement, this.web2SocketExchangeOutboundType);
}
}