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

com.freemanan.starter.grpc.extensions.jsontranscoder.web.WebMvcProtobufHandlerAdaptor Maven / Gradle / Ivy

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;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy