/* * Copyright 2017 original author or 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 * * 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 org.springframework.cloud.reactive.socket; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import reactor.core.publisher.Flux; import org.springframework.cloud.reactive.socket.annotation.Payload; import org.springframework.cloud.reactive.socket.annotation.ReactiveSocket; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotatedElementUtils; /** * Utility class that holds information about a Service method. * @author Vinicius Carvalho */ public class ServiceMethodInfo { private final Method method; private final ServiceMappingInfo mappingInfo; ResolvableType returnType; MethodParameter payloadParameter; ResolvableType payloadType; MethodParameter metadataParameter; public ServiceMethodInfo(Method method) { this.method = method; ReactiveSocket annotated = AnnotatedElementUtils.findMergedAnnotation(method, ReactiveSocket.class); if(annotated == null){ throw new IllegalStateException("Service methods must be annotated with a one of {@OneWayMapping, @RequestOneMapping, @RequestManyMapping, @RequestStreamMapping} "); } this.mappingInfo = new ServiceMappingInfo(annotated.value(), annotated.mimeType(), annotated.exchangeMode()); this.returnType = ResolvableType.forMethodReturnType(method); findPayloadParameter(); validate(); } public void validate(){ switch (this.mappingInfo.getExchangeMode()){ case REQUEST_ONE: if(Void.TYPE.equals(method.getReturnType())){ throw new IllegalArgumentException("Request One methods must return"); } break; case REQUEST_MANY: if(!Flux.class.isAssignableFrom(method.getReturnType())){ throw new IllegalArgumentException("Request Many methods must return a Flux"); } break; case REQUEST_STREAM: if(!Flux.class.isAssignableFrom(method.getReturnType()) || !Flux.class.isAssignableFrom(this.payloadType.resolve())){ throw new IllegalArgumentException("Request Many methods must return and receive a Flux"); } break; } } private void findPayloadParameter(){ if(this.method.getParameterCount() == 0){ throw new IllegalStateException("Service methods must have at least one receiving parameter"); } else if(this.method.getParameterCount() == 1){ this.payloadParameter = new MethodParameter(this.method, 0); } int payloadAnnotations = Flux.just(this.method.getParameters()) .filter(parameter -> parameter.getAnnotation(Payload.class) != null) .reduce(0, (a, parameter) -> { return a+1; }) .block(); if(payloadAnnotations > 1){ throw new IllegalStateException("Service methods can have at most one @Payload annotated parameters"); } for(int i=0; i<this.method.getParameters().length; i++){ Parameter p = this.method.getParameters()[i]; if(p.getAnnotation(Payload.class) != null){ this.payloadParameter = new MethodParameter(this.method, i); break; } } if(this.payloadParameter == null){ throw new IllegalStateException("Service methods annotated with more than one parameter must declare one @Payload parameter"); } resolvePayloadType(); } private void resolvePayloadType(){ this.payloadType = ResolvableType.forMethodParameter(this.method, payloadParameter.getParameterIndex()); } public ResolvableType getReturnType() { return returnType; } public ResolvableType getParameterType() { return payloadType; } public Method getMethod() { return method; } public ServiceMappingInfo getMappingInfo() { return mappingInfo; } public Object[] buildInvocationArguments(Object payload, Object metadata){ Object[] args = new Object[method.getParameterCount()]; for(int i=0; i<args.length; i++){ if(i == payloadParameter.getParameterIndex()){ args[i] = payload; } else if(metadataParameter != null && i == metadataParameter.getParameterIndex()){ args[i] = metadata; } else{ args[i] = getDefaultValue(new MethodParameter(this.method, i)); } } return args; } private Object getDefaultValue(MethodParameter parameter) { if (Boolean.TYPE.equals(parameter.getNestedParameterType())) { return Boolean.FALSE; } return null; } }