void createBindableServiceBeans()

in extensions/grpc/deployment/src/main/java/org/apache/camel/quarkus/component/grpc/deployment/GrpcProcessor.java [106:233]


    void createBindableServiceBeans(
            BuildProducer<GeneratedBeanBuildItem> generatedBean,
            BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
            CombinedIndexBuildItem combinedIndexBuildItem,
            CamelGrpcServiceExcludesBuildItem camelGrpcServiceExcludes) {

        IndexView index = combinedIndexBuildItem.getIndex();
        Collection<ClassInfo> bindableServiceImpls = index.getAllKnownImplementors(BINDABLE_SERVICE_DOT_NAME)
                .stream()
                .filter(camelGrpcServiceExcludes.serviceExcludesFilter())
                .collect(Collectors.toSet());

        // Generate implementation classes from any abstract gRPC BindableService implementations included in the application archive
        // Override the various sync and async methods so that requests can be intercepted and delegated to Camel routing
        // This mimics similar logic in DefaultBindableServiceFactory that uses Javassist ProxyFactory & MethodHandler
        for (ClassInfo service : bindableServiceImpls) {
            String superClassName = service.name().toString();
            String generatedClassName = superClassName + "QuarkusMethodHandler";

            if (!Modifier.isAbstract(service.flags())) {
                logDebugMessage("Ignoring BindableService %s as it is not an interface or abstract class", superClassName);
                continue;
            }

            if (service.name().withoutPackagePrefix().startsWith("Mutiny")) {
                /* The generate-code goal of quarkus-maven-plugin generates also Mutiny service that we do not use
                 * Not skipping it here results in randomly registering the Mutiny one or the right one.
                 * In case the Mutiny service one is registered, the client throws something like
                 * io.grpc.StatusRuntimeException: UNIMPLEMENTED */
                logDebugMessage("Ignoring BindableService %s as it a Mutiny service", superClassName);
                continue;
            }

            Optional<String> asyncServiceInterface = service.interfaceNames()
                    .stream()
                    .map(DotName::toString)
                    .filter(className -> className.endsWith("AsyncService"))
                    .findFirst();
            if (asyncServiceInterface.isEmpty()) {
                logDebugMessage("Ignoring BindableService %s as it does not implement AsyncService", superClassName);
                continue;
            }

            // Register the service classes for reflection
            reflectiveClass
                    .produce(ReflectiveClassBuildItem.builder(superClassName).methods().build());
            reflectiveClass.produce(
                    ReflectiveClassBuildItem.builder(service.enclosingClass().toString()).methods().build());
            reflectiveClass.produce(ReflectiveClassBuildItem.builder(generatedClassName).methods().build());

            logDebugMessage("Generating CamelQuarkusBindableService %s extending %s", generatedClassName, superClassName);

            try (ClassCreator classCreator = ClassCreator.builder()
                    .classOutput(new GeneratedBeanGizmoAdaptor(generatedBean))
                    .className(generatedClassName)
                    .superClass(superClassName)
                    .interfaces(CamelQuarkusBindableService.class)
                    .build()) {

                classCreator.addAnnotation(Dependent.class);

                FieldCreator serverMethodHandler = classCreator
                        .getFieldCreator("methodHandler", GrpcMethodHandler.class.getName())
                        .setModifiers(Modifier.PRIVATE);

                // Create constructor
                try (MethodCreator initMethod = classCreator.getMethodCreator("<init>", void.class)) {
                    initMethod.setModifiers(Modifier.PUBLIC);
                    initMethod.invokeSpecialMethod(MethodDescriptor.ofMethod(superClassName, "<init>", void.class),
                            initMethod.getThis());
                    initMethod.returnValue(null);
                }

                // Create setMethodHandler override
                try (MethodCreator setMethodHandlerMethod = classCreator.getMethodCreator("setMethodHandler", void.class,
                        GrpcMethodHandler.class)) {
                    setMethodHandlerMethod.setModifiers(Modifier.PUBLIC);

                    ResultHandle self = setMethodHandlerMethod.getThis();
                    ResultHandle methodHandlerInstance = setMethodHandlerMethod.getMethodParam(0);

                    setMethodHandlerMethod.writeInstanceField(serverMethodHandler.getFieldDescriptor(), self,
                            methodHandlerInstance);
                    setMethodHandlerMethod.returnValue(null);
                }

                // Override service methods that the gRPC component is interested in
                // E.g methods with one or two parameters where one is of type StreamObserver
                ClassInfo asyncServiceClassInfo = index.getClassByName(asyncServiceInterface.get());
                List<MethodInfo> methods = asyncServiceClassInfo.methods();
                for (MethodInfo method : methods) {
                    if (isCandidateServiceMethod(method)) {
                        String[] params = method.parameters()
                                .stream()
                                .map(MethodParameterInfo::type)
                                .map(Type::name)
                                .map(DotName::toString)
                                .toArray(String[]::new);

                        ClassInfo classInfo = index
                                .getClassByName(DotName.createSimple(GrpcMethodHandler.class.getName()));

                        String returnType = method.returnType().name().toString();
                        try (MethodCreator methodCreator = classCreator.getMethodCreator(method.name(), returnType, params)) {
                            logDebugMessage("Creating service implementation for method %s in %s", method.name(),
                                    generatedClassName);

                            method.exceptions()
                                    .stream()
                                    .map(type -> type.name().toString())
                                    .forEach(methodCreator::addException);

                            if (method.parameters().size() == 1) {
                                ResultHandle returnValue = generateGrpcDelegateMethod(classInfo, serverMethodHandler,
                                        methodCreator,
                                        method, "handleForConsumerStrategy");
                                methodCreator.returnValue(returnValue);
                            } else if (method.parameters().size() == 2) {
                                generateGrpcDelegateMethod(classInfo, serverMethodHandler, methodCreator, method,
                                        "handle");
                                methodCreator.returnValue(null);
                            }
                        }
                    }
                }
            }
        }
    }