of(A a, B b, C c) {
- return new Tuple3<>(a, b, c);
- }
-}
diff --git a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/web/WebMvcGrpcServiceHandlerMapping.java b/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/web/WebMvcGrpcServiceHandlerMapping.java
deleted file mode 100644
index 69de2980..00000000
--- a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/web/WebMvcGrpcServiceHandlerMapping.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.freemanan.starter.grpc.extensions.jsontranscoder.web;
-
-import com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil;
-import io.grpc.BindableService;
-import jakarta.servlet.http.HttpServletRequest;
-import java.util.Map;
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.http.HttpMethod;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
-import org.springframework.web.servlet.handler.AbstractHandlerMapping;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
-
-/**
- * Provide default routes for those gRPC services without custom http routes.
- *
- * The default route is: POST /xx.v1.ServiceName/MethodName
- *
- * @author Freeman
- */
-public class WebMvcGrpcServiceHandlerMapping extends AbstractHandlerMapping {
- public static final int ORDER = 10;
-
- /**
- * path -> service
- *
- *
path: /xx.v1.ServiceName/MethodName
- */
- private final Map pathToMethod;
-
- public WebMvcGrpcServiceHandlerMapping(ObjectProvider grpcServiceProvider) {
- this.pathToMethod = JsonTranscoderUtil.getPathToMethod(grpcServiceProvider);
- }
-
- @Override
- protected HandlerMethod getHandlerInternal(HttpServletRequest request) {
- if (!HttpMethod.POST.name().equalsIgnoreCase(request.getMethod())) {
- return null;
- }
- for (Map.Entry entry : pathToMethod.entrySet()) {
- if (entry.getKey().equalsIgnoreCase(request.getRequestURI())) {
- return entry.getValue();
- }
- }
- return null;
- }
-
- /**
- * Must after {@link RequestMappingHandlerMapping}, it will process {@link RequestMapping} for us for free!
- *
- * @see WebMvcConfigurationSupport#requestMappingHandlerMapping
- */
- @Override
- public int getOrder() {
- return ORDER;
- }
-}
diff --git a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/web/WebMvcProtobufHandlerAdaptor.java b/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/web/WebMvcProtobufHandlerAdaptor.java
deleted file mode 100644
index 99ec9c36..00000000
--- a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/web/WebMvcProtobufHandlerAdaptor.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package com.freemanan.starter.grpc.extensions.jsontranscoder.web;
-
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil.anyCompatible;
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil.getAccept;
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil.isGrpcHandleMethod;
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil.isJson;
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil.notAcceptableException;
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.ProtoUtil.toJson;
-import static org.springframework.http.MediaType.APPLICATION_JSON;
-
-import com.freemanan.starter.grpc.extensions.jsontranscoder.AbstractHandlerAdaptor;
-import com.freemanan.starter.grpc.extensions.jsontranscoder.GrpcHeaderConverter;
-import com.google.protobuf.Message;
-import io.grpc.Metadata;
-import io.grpc.StatusRuntimeException;
-import io.grpc.stub.MetadataUtils;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.nio.charset.StandardCharsets;
-import java.util.concurrent.atomic.AtomicReference;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.server.ServletServerHttpRequest;
-import org.springframework.http.server.ServletServerHttpResponse;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.HandlerAdapter;
-import org.springframework.web.servlet.ModelAndView;
-import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter;
-import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
-
-/**
- * @author Freeman
- */
-public class WebMvcProtobufHandlerAdaptor extends AbstractHandlerAdaptor implements HandlerAdapter {
-
- private static final String NEW_BLOCKING_STUB = "newBlockingStub";
-
- private final GrpcHeaderConverter grpcHeaderConverter;
-
- public WebMvcProtobufHandlerAdaptor(GrpcHeaderConverter grpcHeaderConverter) {
- this.grpcHeaderConverter = grpcHeaderConverter;
- }
-
- @Override
- public boolean supports(Object handler) {
- return isGrpcHandleMethod(handler);
- }
-
- @Override
- public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
- throws Exception {
- HandlerMethod hm = (HandlerMethod) handler;
- Method method = hm.getMethod();
- Class> beanClass = hm.getBeanType();
-
- // create message
- Message message = convert2ProtobufMessage(method.getParameterTypes()[0], request.getInputStream());
-
- // create metadata
- ServletServerHttpRequest req = new ServletServerHttpRequest(request);
- Metadata metadata = grpcHeaderConverter.toRequestMetadata(req.getHeaders());
-
- // get gRPC blocking stub to use
- Object stub = getStub(beanClass);
-
- // apply metadata to stub
- stub = applyInterceptor4Stub(MetadataUtils.newAttachHeadersInterceptor(metadata), stub);
-
- // capture gRPC response header/trailer
- AtomicReference responseHeader = new AtomicReference<>();
- AtomicReference responseTrailer = new AtomicReference<>();
- stub = applyInterceptor4Stub(
- MetadataUtils.newCaptureMetadataInterceptor(responseHeader, responseTrailer), stub);
-
- // find gRPC stub method to call
- Method stubMethod = getInvokeMethod(stub, method, message);
-
- // call gRPC stub method
- Message grpcResponse;
- try {
- grpcResponse = (Message) stubMethod.invoke(stub, message);
- } catch (InvocationTargetException ite) {
- Throwable te = ite.getTargetException();
- if (te instanceof StatusRuntimeException) {
- throw (StatusRuntimeException) te;
- }
- throw ite;
- }
-
- // convert gRPC response header to HTTP header
- Metadata responseMetadata = responseHeader.get();
- if (responseMetadata != null) {
- HttpHeaders headers = grpcHeaderConverter.toResponseHeader(responseMetadata);
- headers.forEach((k, values) -> values.forEach(v -> response.addHeader(k, v)));
- }
-
- try (ServletServerHttpResponse resp = new ServletServerHttpResponse(response)) {
- // convert gRPC response message (Protobuf) to JSON
- String json = toJson(grpcResponse);
- if (isJson(json)) {
- if (anyCompatible(getAccept(req.getHeaders()), APPLICATION_JSON)) {
- resp.getHeaders().setContentType(APPLICATION_JSON);
- resp.getBody().write(json.getBytes(StandardCharsets.UTF_8));
- resp.getBody().flush();
- return null;
- }
- throw notAcceptableException();
- }
-
- MediaType mt = new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8);
- if (anyCompatible(getAccept(req.getHeaders()), mt)) {
- resp.getHeaders().setContentType(mt);
- resp.getBody().write(json.getBytes(StandardCharsets.UTF_8));
- resp.getBody().flush();
- return null;
- }
- throw notAcceptableException();
- }
- }
-
- /**
- * @see RequestMappingHandlerAdapter#getLastModifiedInternal
- */
- @Override
- @Deprecated
- public long getLastModified(HttpServletRequest request, Object handler) {
- return -1;
- }
-
- /**
- * Must before {@link RequestMappingHandlerAdapter}
- *
- * @see AbstractHandlerMethodAdapter#getOrder()
- */
- @Override
- public int getOrder() {
- return ORDER;
- }
-
- @Override
- public String getNewStubMethodName() {
- return NEW_BLOCKING_STUB;
- }
-}
diff --git a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/ControllerMethodResolver.java b/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/ControllerMethodResolver.java
deleted file mode 100644
index f003672c..00000000
--- a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/ControllerMethodResolver.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright 2002-2022 the 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
- *
- * 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 com.freemanan.starter.grpc.extensions.jsontranscoder.webflux;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.core.KotlinDetector;
-import org.springframework.core.ReactiveAdapterRegistry;
-import org.springframework.http.codec.HttpMessageReader;
-import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
-import org.springframework.web.method.ControllerAdviceBean;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
-import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
-import org.springframework.web.reactive.result.method.annotation.ContinuationHandlerMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.CookieValueMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.ErrorsMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.ExpressionValueMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.HttpEntityMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.MatrixVariableMapMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.MatrixVariableMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.ModelMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.PathVariableMapMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.PathVariableMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.PrincipalMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.RequestAttributeMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.RequestBodyMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.RequestHeaderMapMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.RequestHeaderMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.RequestParamMapMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.RequestParamMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.RequestPartMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.ServerWebExchangeMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.SessionAttributeMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.SessionStatusMethodArgumentResolver;
-import org.springframework.web.reactive.result.method.annotation.WebSessionMethodArgumentResolver;
-
-/**
- * Copy form {@link org.springframework.web.reactive.result.method.annotation.ControllerMethodResolver}.
- *
- * @author Freeman
- */
-class ControllerMethodResolver {
-
- private final List exceptionHandlerResolvers;
-
- private final Map, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
-
- private final Map exceptionHandlerAdviceCache =
- new LinkedHashMap<>(64);
-
- @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
- ControllerMethodResolver(
- ReactiveAdapterRegistry adapterRegistry,
- ConfigurableApplicationContext context,
- List> readers) {
-
- Assert.notNull(context, "ApplicationContext is required");
- Assert.notNull(readers, "HttpMessageReader List is required");
-
- this.exceptionHandlerResolvers = exceptionHandlerResolvers(adapterRegistry, context);
-
- initControllerAdviceCaches(context);
- }
-
- private static List exceptionHandlerResolvers(
- ReactiveAdapterRegistry adapterRegistry, ConfigurableApplicationContext context) {
-
- return initResolvers(adapterRegistry, context, false, Collections.emptyList());
- }
-
- private static List initResolvers(
- ReactiveAdapterRegistry adapterRegistry,
- ConfigurableApplicationContext context,
- boolean supportDataBinding,
- List> readers) {
-
- ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
- boolean requestMappingMethod = !readers.isEmpty() && supportDataBinding;
-
- // Annotation-based...
- List result = new ArrayList<>(30);
- result.add(new RequestParamMethodArgumentResolver(beanFactory, adapterRegistry, false));
- result.add(new RequestParamMapMethodArgumentResolver(adapterRegistry));
- result.add(new PathVariableMethodArgumentResolver(beanFactory, adapterRegistry));
- result.add(new PathVariableMapMethodArgumentResolver(adapterRegistry));
- result.add(new MatrixVariableMethodArgumentResolver(beanFactory, adapterRegistry));
- result.add(new MatrixVariableMapMethodArgumentResolver(adapterRegistry));
- if (!readers.isEmpty()) {
- result.add(new RequestBodyMethodArgumentResolver(readers, adapterRegistry));
- result.add(new RequestPartMethodArgumentResolver(readers, adapterRegistry));
- }
- if (supportDataBinding) {
- result.add(new ModelAttributeMethodArgumentResolver(adapterRegistry, false));
- }
- result.add(new RequestHeaderMethodArgumentResolver(beanFactory, adapterRegistry));
- result.add(new RequestHeaderMapMethodArgumentResolver(adapterRegistry));
- result.add(new CookieValueMethodArgumentResolver(beanFactory, adapterRegistry));
- result.add(new ExpressionValueMethodArgumentResolver(beanFactory, adapterRegistry));
- result.add(new SessionAttributeMethodArgumentResolver(beanFactory, adapterRegistry));
- result.add(new RequestAttributeMethodArgumentResolver(beanFactory, adapterRegistry));
-
- // Type-based...
- if (!readers.isEmpty()) {
- result.add(new HttpEntityMethodArgumentResolver(readers, adapterRegistry));
- }
- result.add(new ModelMethodArgumentResolver(adapterRegistry));
- if (supportDataBinding) {
- result.add(new ErrorsMethodArgumentResolver(adapterRegistry));
- }
- result.add(new ServerWebExchangeMethodArgumentResolver(adapterRegistry));
- result.add(new PrincipalMethodArgumentResolver(adapterRegistry));
- if (requestMappingMethod) {
- result.add(new SessionStatusMethodArgumentResolver());
- }
- result.add(new WebSessionMethodArgumentResolver(adapterRegistry));
- if (KotlinDetector.isKotlinPresent()) {
- result.add(new ContinuationHandlerMethodArgumentResolver());
- }
-
- // Custom...
- // result.addAll(customResolvers.getCustomResolvers());
-
- // Catch-all...
- result.add(new RequestParamMethodArgumentResolver(beanFactory, adapterRegistry, true));
- if (supportDataBinding) {
- result.add(new ModelAttributeMethodArgumentResolver(adapterRegistry, true));
- }
-
- return result;
- }
-
- private void initControllerAdviceCaches(ApplicationContext applicationContext) {
- List beans = ControllerAdviceBean.findAnnotatedBeans(applicationContext);
- for (ControllerAdviceBean bean : beans) {
- Class> beanType = bean.getBeanType();
- if (beanType != null) {
- ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
- if (resolver.hasExceptionMappings()) {
- this.exceptionHandlerAdviceCache.put(bean, resolver);
- }
- }
- }
- }
-
- /**
- * Look for an {@code @ExceptionHandler} method within the class of the given
- * controller method, and also within {@code @ControllerAdvice} classes that
- * are applicable to the class of the given controller method.
- *
- * @param ex the exception to find a handler for
- * @param handlerMethod the controller method that raised the exception, or
- * if {@code null}, check only {@code @ControllerAdvice} classes.
- */
- @Nullable
- public InvocableHandlerMethod getExceptionHandlerMethod(Throwable ex, @Nullable HandlerMethod handlerMethod) {
-
- Class> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
- Object exceptionHandlerObject = null;
- Method exceptionHandlerMethod = null;
-
- if (handlerType != null) {
- // Controller-local first...
- exceptionHandlerObject = handlerMethod.getBean();
- exceptionHandlerMethod = this.exceptionHandlerCache
- .computeIfAbsent(handlerType, ExceptionHandlerMethodResolver::new)
- .resolveMethodByThrowable(ex);
- }
-
- if (exceptionHandlerMethod == null) {
- // Global exception handlers...
- for (Map.Entry entry :
- this.exceptionHandlerAdviceCache.entrySet()) {
- ControllerAdviceBean advice = entry.getKey();
- if (advice.isApplicableToBeanType(handlerType)) {
- exceptionHandlerMethod = entry.getValue().resolveMethodByThrowable(ex);
- if (exceptionHandlerMethod != null) {
- exceptionHandlerObject = advice.resolveBean();
- break;
- }
- }
- }
- }
-
- if (exceptionHandlerObject == null || exceptionHandlerMethod == null) {
- return null;
- }
-
- InvocableHandlerMethod invocable = new InvocableHandlerMethod(exceptionHandlerObject, exceptionHandlerMethod);
- invocable.setArgumentResolvers(this.exceptionHandlerResolvers);
- return invocable;
- }
-}
diff --git a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/GrpcHandlerResultHandler.java b/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/GrpcHandlerResultHandler.java
deleted file mode 100644
index a30b862e..00000000
--- a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/GrpcHandlerResultHandler.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.freemanan.starter.grpc.extensions.jsontranscoder.webflux;
-
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil.isJson;
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.ProtoUtil.toJson;
-
-import com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil;
-import com.google.protobuf.Message;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.function.Supplier;
-import org.springframework.core.MethodParameter;
-import org.springframework.core.Ordered;
-import org.springframework.http.MediaType;
-import org.springframework.web.reactive.HandlerResult;
-import org.springframework.web.reactive.HandlerResultHandler;
-import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-
-/**
- * @author Freeman
- */
-public class GrpcHandlerResultHandler implements HandlerResultHandler, Ordered {
-
- public static final int ORDER = 0;
-
- @Override
- public boolean supports(HandlerResult result) {
- MethodParameter retType = result.getReturnTypeSource();
- return retType.getParameterType() == Void.TYPE && result.getReturnValue() instanceof Message;
- }
-
- @Override
- public Mono handleResult(ServerWebExchange exchange, HandlerResult result) {
- Message message = (Message) result.getReturnValue();
- Supplier errorSupplier = JsonTranscoderUtil::notAcceptableException;
- String json = toJson(message);
- if (isJson(json)) {
- if (anyCompatible(exchange, MediaType.APPLICATION_JSON)) {
- exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
- return exchange.getResponse()
- .writeWith(Mono.just(
- exchange.getResponse().bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8))));
- }
- return Mono.error(errorSupplier);
- }
- // simple value using content-type text/plain;charset=UTF-8
- MediaType mt = new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8);
- if (anyCompatible(exchange, mt)) {
- exchange.getResponse().getHeaders().setContentType(mt);
- return exchange.getResponse()
- .writeWith(Mono.just(
- exchange.getResponse().bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8))));
- }
- return Mono.error(errorSupplier);
- }
-
- private static boolean anyCompatible(ServerWebExchange exchange, MediaType otherMediaType) {
- List mediaTypes =
- JsonTranscoderUtil.getAccept(exchange.getRequest().getHeaders());
- for (MediaType mediaType : mediaTypes) {
- if (mediaType.isCompatibleWith(otherMediaType)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * {@link ResponseBodyResultHandler} order is 100.
- *
- * @see ResponseBodyResultHandler
- */
- @Override
- public int getOrder() {
- return ORDER;
- }
-}
diff --git a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/WebFluxGrpcServiceHandlerMapping.java b/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/WebFluxGrpcServiceHandlerMapping.java
deleted file mode 100644
index c41633ae..00000000
--- a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/WebFluxGrpcServiceHandlerMapping.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.freemanan.starter.grpc.extensions.jsontranscoder.webflux;
-
-import com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil;
-import io.grpc.BindableService;
-import java.util.Map;
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.http.HttpMethod;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
-import org.springframework.web.reactive.handler.AbstractHandlerMapping;
-import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-
-/**
- * Provide default routes for those gRPC services without custom http routes.
- *
- * The default route is: POST /xx.v1.ServiceName/MethodName
- *
- * @author Freeman
- */
-public class WebFluxGrpcServiceHandlerMapping extends AbstractHandlerMapping {
-
- public static final int ORDER = 10;
-
- /**
- * path -> service
- *
- *
path: /xx.v1.ServiceName/MethodName
- */
- private final Map pathToMethod;
-
- public WebFluxGrpcServiceHandlerMapping(ObjectProvider grpcServiceProvider) {
- this.pathToMethod = JsonTranscoderUtil.getPathToMethod(grpcServiceProvider);
- }
-
- @Override
- protected Mono> getHandlerInternal(ServerWebExchange exchange) {
- HttpMethod method = exchange.getRequest().getMethod();
- if (method != HttpMethod.POST) {
- return Mono.empty();
- }
- String path = exchange.getRequest().getPath().toString();
- for (Map.Entry entry : pathToMethod.entrySet()) {
- if (entry.getKey().equalsIgnoreCase(path)) {
- return Mono.just(entry.getValue());
- }
- }
- return Mono.empty();
- }
-
- /**
- * Must after {@link RequestMappingHandlerMapping}, it will process {@link RequestMapping} for us for free!
- *
- * @see WebFluxConfigurationSupport#requestMappingHandlerMapping
- */
- @Override
- public int getOrder() {
- return ORDER;
- }
-}
diff --git a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/WebFluxProtobufHandlerAdaptor.java b/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/WebFluxProtobufHandlerAdaptor.java
deleted file mode 100644
index 82c6ce18..00000000
--- a/grpc-extensions/grpc-json-transcoder/src/main/java/com/freemanan/starter/grpc/extensions/jsontranscoder/webflux/WebFluxProtobufHandlerAdaptor.java
+++ /dev/null
@@ -1,182 +0,0 @@
-package com.freemanan.starter.grpc.extensions.jsontranscoder.webflux;
-
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.util.JsonTranscoderUtil.isGrpcHandleMethod;
-
-import com.freemanan.starter.grpc.extensions.jsontranscoder.AbstractHandlerAdaptor;
-import com.freemanan.starter.grpc.extensions.jsontranscoder.FutureAdapter;
-import com.freemanan.starter.grpc.extensions.jsontranscoder.GrpcHeaderConverter;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.protobuf.Message;
-import io.grpc.Metadata;
-import io.grpc.stub.MetadataUtils;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.core.ReactiveAdapterRegistry;
-import org.springframework.core.io.buffer.DataBufferUtils;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.codec.HttpMessageReader;
-import org.springframework.lang.Nullable;
-import org.springframework.util.ReflectionUtils;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.reactive.BindingContext;
-import org.springframework.web.reactive.DispatchExceptionHandler;
-import org.springframework.web.reactive.HandlerAdapter;
-import org.springframework.web.reactive.HandlerMapping;
-import org.springframework.web.reactive.HandlerResult;
-import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
-import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
-
-/**
- * @author Freeman
- */
-public class WebFluxProtobufHandlerAdaptor extends AbstractHandlerAdaptor
- implements HandlerAdapter, DispatchExceptionHandler {
- private static final Logger log = LoggerFactory.getLogger(WebFluxProtobufHandlerAdaptor.class);
-
- private static final String NEW_FUTURE_STUB = "newFutureStub";
-
- private final ControllerMethodResolver resolver;
- private final GrpcHeaderConverter grpcHeaderConverter;
-
- public WebFluxProtobufHandlerAdaptor(
- ReactiveAdapterRegistry adapterRegistry,
- ConfigurableApplicationContext context,
- List> readers,
- GrpcHeaderConverter grpcHeaderConverter) {
- this.resolver = new ControllerMethodResolver(adapterRegistry, context, readers);
- this.grpcHeaderConverter = grpcHeaderConverter;
- }
-
- @SuppressWarnings("unchecked")
- private static ListenableFuture getFutureStubResponse(Object futureStub, Message msg, Method method) {
- return (ListenableFuture) ReflectionUtils.invokeMethod(method, futureStub, msg);
- }
-
- @Override
- public boolean supports(Object handler) {
- return isGrpcHandleMethod(handler);
- }
-
- @Override
- public Mono handle(ServerWebExchange exchange, Object handler) {
- HandlerMethod hm = ((HandlerMethod) handler);
- Method method = hm.getMethod();
- Class> messageType = method.getParameterTypes()[0];
- Class> beanClass = hm.getBeanType();
-
- DispatchExceptionHandler exceptionHandler =
- (exchange2, ex) -> handleException(exchange, ex, (HandlerMethod) handler, new BindingContext());
-
- AtomicReference responseHeader = new AtomicReference<>();
- AtomicReference responseTrailer = new AtomicReference<>();
-
- return DataBufferUtils.join(exchange.getRequest().getBody())
- .map(dataBuffer -> convert2ProtobufMessage(messageType, dataBuffer.asInputStream()))
- // invoke grpc method
- .flatMap(msg -> {
- Object stub = getStub(beanClass);
-
- // make sure headers are modifiable
- HttpHeaders headers = new HttpHeaders();
- headers.putAll(exchange.getRequest().getHeaders());
- Metadata metadata = grpcHeaderConverter.toRequestMetadata(headers);
-
- // apply metadata to stub
- stub = applyInterceptor4Stub(MetadataUtils.newAttachHeadersInterceptor(metadata), stub);
-
- // capture gRPC response header/trailer
- stub = applyInterceptor4Stub(
- MetadataUtils.newCaptureMetadataInterceptor(responseHeader, responseTrailer), stub);
-
- Method m = getInvokeMethod(stub, method, msg);
- ListenableFuture resp = getFutureStubResponse(stub, msg, m);
- return Mono.fromFuture(FutureAdapter.toCompletable(resp));
- })
- .doOnNext(msg -> {
- // convert gRPC response header to HTTP header
- Metadata responseMetadata = responseHeader.get();
- if (responseMetadata != null) {
- HttpHeaders headers = grpcHeaderConverter.toResponseHeader(responseMetadata);
- headers.forEach((k, values) -> values.forEach(
- v -> exchange.getResponse().getHeaders().add(k, v)));
- }
- })
- .map(message -> new HandlerResult(hm, message, hm.getReturnType()))
- .doOnNext(handlerResult -> handlerResult.setExceptionHandler(exceptionHandler))
- .onErrorResume(ex -> exceptionHandler.handleError(exchange, ex));
- }
-
- private Mono handleException(
- ServerWebExchange exchange,
- Throwable exception,
- @Nullable HandlerMethod handlerMethod,
- @Nullable BindingContext bindingContext) {
-
- // Success and error responses may use different content types
- exchange.getAttributes().remove(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
- exchange.getResponse().getHeaders().clearContentHeaders();
-
- InvocableHandlerMethod invocable = this.resolver.getExceptionHandlerMethod(exception, handlerMethod);
-
- if (invocable != null) {
- ArrayList exceptions = new ArrayList<>();
- try {
- if (log.isDebugEnabled()) {
- log.debug("{} using @ExceptionHandler {}", exchange.getLogPrefix(), invocable);
- }
- if (bindingContext != null) {
- bindingContext.getModel().asMap().clear();
- } else {
- bindingContext = new BindingContext();
- }
-
- // Expose causes as provided arguments as well
- Throwable exToExpose = exception;
- while (exToExpose != null) {
- exceptions.add(exToExpose);
- Throwable cause = exToExpose.getCause();
- exToExpose = (cause != exToExpose ? cause : null);
- }
- Object[] arguments = new Object[exceptions.size() + 1];
- exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
- arguments[arguments.length - 1] = handlerMethod;
-
- return invocable.invoke(exchange, bindingContext, arguments);
- } catch (Throwable invocationEx) {
- // Any other than the original exception (or a cause) is unintended here,
- // probably an accident (e.g. failed assertion or the like).
- if (!exceptions.contains(invocationEx) && log.isWarnEnabled()) {
- log.warn(exchange.getLogPrefix() + "Failure in @ExceptionHandler " + invocable, invocationEx);
- }
- }
- }
- return Mono.error(exception);
- }
-
- /**
- * Must before {@link RequestMappingHandlerAdapter}!
- *
- * @see RequestMappingHandlerAdapter
- */
- @Override
- public int getOrder() {
- return ORDER;
- }
-
- @Override
- public String getNewStubMethodName() {
- return NEW_FUTURE_STUB;
- }
-
- @Override
- public Mono handleError(ServerWebExchange exchange, Throwable ex) {
- return handleException(exchange, ex, null, null);
- }
-}
diff --git a/grpc-extensions/grpc-json-transcoder/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/grpc-extensions/grpc-json-transcoder/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
deleted file mode 100644
index b6e429c2..00000000
--- a/grpc-extensions/grpc-json-transcoder/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ /dev/null
@@ -1 +0,0 @@
-com.freemanan.starter.grpc.extensions.jsontranscoder.GrpcJsonTranscoderAutoConfiguration
\ No newline at end of file
diff --git a/grpc-extensions/grpc-json-transcoder/src/test/java/com/freemanan/starter/grpc/extensions/jsontranscoder/JsonTranscoderIT.java b/grpc-extensions/grpc-json-transcoder/src/test/java/com/freemanan/starter/grpc/extensions/jsontranscoder/JsonTranscoderIT.java
deleted file mode 100644
index 835828d2..00000000
--- a/grpc-extensions/grpc-json-transcoder/src/test/java/com/freemanan/starter/grpc/extensions/jsontranscoder/JsonTranscoderIT.java
+++ /dev/null
@@ -1,256 +0,0 @@
-package com.freemanan.starter.grpc.extensions.jsontranscoder;
-
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.Deps.WEB_FLUX_STARTER;
-import static com.freemanan.starter.grpc.extensions.jsontranscoder.Deps.WEB_MVC_STARTER;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.freemanan.cr.core.anno.Action;
-import com.freemanan.cr.core.anno.ClasspathReplacer;
-import com.freemanan.sample.pet.v1.GetPetRequest;
-import com.freemanan.sample.pet.v1.Pet;
-import com.freemanan.sample.pet.v1.PetServiceGrpc;
-import com.freemanan.starter.grpc.server.GrpcService;
-import com.google.protobuf.StringValue;
-import io.grpc.ForwardingServerCall;
-import io.grpc.Metadata;
-import io.grpc.ServerCall;
-import io.grpc.ServerCallHandler;
-import io.grpc.ServerInterceptor;
-import io.grpc.stub.StreamObserver;
-import java.nio.charset.StandardCharsets;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.builder.SpringApplicationBuilder;
-import org.springframework.boot.test.web.client.TestRestTemplate;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.test.web.reactive.server.WebTestClient;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RestControllerAdvice;
-
-/**
- * @author Freeman
- */
-class JsonTranscoderIT {
-
- @Test
- @ClasspathReplacer(@Action(WEB_FLUX_STARTER))
- void testWebFluxTranscoderJson() {
- int port = U.randomPort();
- ConfigurableApplicationContext ctx = new SpringApplicationBuilder(Cfg.class)
- .properties("server.port=" + port)
- .run();
-
- WebTestClient client = U.webclient(port);
-
- // test native path
- WebTestClient.ResponseSpec resp = client.post()
- .uri("/sample.pet.v1.PetService/GetPet")
- .contentType(MediaType.APPLICATION_JSON)
- .bodyValue("{\"name\":\"pet\"}")
- .exchange();
- resp.expectStatus().isOk();
- resp.expectHeader().contentType(MediaType.APPLICATION_JSON);
- resp.expectHeader().valueEquals("request-id", "001");
- resp.expectBody().json("{\"name\":\"pet\",\"age\":1}");
-
- // test path alias
- resp = client.post()
- .uri("/v1/pets/get")
- .contentType(MediaType.APPLICATION_JSON)
- .bodyValue("{\"name\":\"pet\"}")
- .exchange();
- resp.expectStatus().isOk();
- resp.expectHeader().contentType(MediaType.APPLICATION_JSON);
- resp.expectHeader().valueEquals("request-id", "001");
- resp.expectBody().json("{\"name\":\"pet\",\"age\":1}");
-
- ctx.close();
- }
-
- @Test
- @ClasspathReplacer(@Action(WEB_FLUX_STARTER))
- void testWebFluxTranscoderJson_whenSimpleValue() {
- int port = U.randomPort();
- ConfigurableApplicationContext ctx = new SpringApplicationBuilder(Cfg.class)
- .properties("server.port=" + port)
- .run();
-
- WebTestClient client = U.webclient(port);
-
- // wrapper type parses as JSON is simple value format
- // google.protobuf.StringValue convert to JSON, the result is "foo", not {"value":"foo"}
- // So, when convert JSON string to google.protobuf.StringValue, the input string must be "foo", not
- // {"value":"foo"}
- WebTestClient.ResponseSpec resp = client.post()
- .uri("/sample.pet.v1.PetService/GetPetName")
- .bodyValue("\"Freeman\"")
- .exchange();
- resp.expectStatus().isOk();
- resp.expectHeader().contentType(new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8));
- resp.expectBody(String.class).isEqualTo("\"Freeman\"");
-
- // test wrong format
- resp = client.post()
- .uri("/sample.pet.v1.PetService/GetPetName")
- .contentType(MediaType.APPLICATION_JSON)
- .bodyValue("{\"value\":\"Freeman\"}")
- .exchange();
- resp.expectStatus().is4xxClientError();
-
- ctx.close();
- }
-
- // @Test
- @ClasspathReplacer(@Action(WEB_FLUX_STARTER))
- void testWebFluxExceptionHandling() {
- int port = U.randomPort();
- ConfigurableApplicationContext ctx = new SpringApplicationBuilder(Cfg.class)
- .properties("server.port=" + port)
- .run();
-
- WebTestClient client = U.webclient(port);
-
- // test native path
- WebTestClient.ResponseSpec resp = client.post()
- .uri("/sample.pet.v1.PetService/GetPet")
- .contentType(MediaType.APPLICATION_JSON)
- .bodyValue("{\"name\":\"error\"}")
- .exchange();
- resp.expectStatus().is5xxServerError();
- resp.expectHeader().contentType(MediaType.APPLICATION_JSON);
- resp.expectHeader().doesNotExist("request-id");
- resp.expectBody().json("{\"code\":2,\"data\":null,\"message\":\"UNKNOWN\"}");
-
- ctx.close();
- }
-
- // ===========================
- // Web Mvc
- // ===========================
-
- @Test
- @ClasspathReplacer(@Action(WEB_MVC_STARTER))
- void testWebMvcTranscoderJson() {
- int port = U.randomPort();
- ConfigurableApplicationContext ctx = new SpringApplicationBuilder(Cfg.class)
- .properties("server.port=" + port)
- .run();
-
- TestRestTemplate client = U.restTemplate();
-
- // test native path
- ResponseEntity resp = client.exchange(
- "http://localhost:" + port + "/sample.pet.v1.PetService/GetPet",
- HttpMethod.POST,
- new HttpEntity<>("{\"name\":\"pet\"}"),
- String.class);
- assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
- assertThat(resp.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
- assertThat(resp.getHeaders().get("request-id")).containsExactly("001");
- assertThat(resp.getBody()).isNotBlank();
- assertThat(resp.getBody().replaceAll("\\s+", "")).isEqualTo("{\"name\":\"pet\",\"age\":1}");
-
- // test path alias
- resp = client.exchange(
- "http://localhost:" + port + "/v1/pets/get",
- HttpMethod.POST,
- new HttpEntity<>("{\"name\":\"pet\"}"),
- String.class);
- assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
- assertThat(resp.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
- assertThat(resp.getHeaders().get("request-id")).containsExactly("001");
- assertThat(resp.getBody()).isNotBlank();
- assertThat(resp.getBody().replaceAll("\\s+", "")).isEqualTo("{\"name\":\"pet\",\"age\":1}");
-
- // wrapper type parses as JSON is simple value format
- // google.protobuf.StringValue convert to JSON, the result is "foo", not {"value":"foo"}
- // So, when convert JSON string to google.protobuf.StringValue, the input string must be "foo", not
- // {"value":"foo"}
- resp = client.exchange(
- "http://localhost:" + port + "/sample.pet.v1.PetService/GetPetName",
- HttpMethod.POST,
- new HttpEntity<>("\"Freeman\""),
- String.class);
- assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
- assertThat(resp.getHeaders().getContentType())
- .isEqualTo(new MediaType(MediaType.TEXT_PLAIN, StandardCharsets.UTF_8));
- assertThat(resp.getBody()).isNotBlank();
- assertThat(resp.getBody()).isEqualTo("\"Freeman\"");
-
- // test wrong format
- resp = client.exchange(
- "http://localhost:" + port + "/sample.pet.v1.PetService/GetPetName",
- HttpMethod.POST,
- new HttpEntity<>("{\"value\":\"Freeman\"}"),
- String.class);
- assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
-
- // test exception handling
- // resp = client.exchange(
- // "http://localhost:" + port + "/sample.pet.v1.PetService/GetPet",
- // HttpMethod.POST,
- // new HttpEntity<>("{\"name\":\"error\"}"),
- // String.class);
- // assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
- // assertThat(resp.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
- // assertThat(resp.getHeaders()).doesNotContainKey("request-id");
- // assertThat(resp.getBody()).isEqualTo("{\"code\":2,\"data\":null,\"message\":\"UNKNOWN\"}");
-
- ctx.close();
- }
-
- @Configuration(proxyBeanMethods = false)
- @EnableAutoConfiguration
- @GrpcService
- @RestControllerAdvice
- static class Cfg extends PetServiceGrpc.PetServiceImplBase implements ServerInterceptor {
- @Override
- @PostMapping("/v1/pets/get")
- public void getPet(GetPetRequest request, StreamObserver ro) {
- if (request.getName().startsWith("err")) {
- throw new IllegalArgumentException("invalid name: " + request.getName());
- }
- ro.onNext(Pet.newBuilder().setName(request.getName()).setAge(1).build());
- ro.onCompleted();
- }
-
- @Override
- public void getPetName(StringValue request, StreamObserver ro) {
- ro.onNext(StringValue.of(request.getValue()));
- ro.onCompleted();
- }
-
- @Override
- public ServerCall.Listener interceptCall(
- ServerCall call, Metadata headers, ServerCallHandler next) {
- ForwardingServerCall.SimpleForwardingServerCall c =
- new ForwardingServerCall.SimpleForwardingServerCall<>(call) {
- @Override
- public void sendHeaders(Metadata headers) {
- headers.put(Metadata.Key.of("request-id", Metadata.ASCII_STRING_MARSHALLER), "001");
- super.sendHeaders(headers);
- }
- };
- return next.startCall(c, headers);
- }
-
- /**
- * TODO(Freeman): why use './gradlew build' will occur ClassNotFound exception (org.springframework.http.HttpStatus)?
- * but use IDEA run test is ok.
- */
- // @ExceptionHandler
- // public ResponseEntity