blob: 474625f19f915805a1fd1499b46266c09b01c687 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.component.grpc;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import io.grpc.CallCredentials;
import io.grpc.Channel;
import io.grpc.stub.StreamObserver;
import org.apache.camel.CamelContext;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.ReflectionHelper;
/**
* GrpcUtils helpers are working with dynamic methods via Camel and
* Java reflection utilities
*/
public final class GrpcUtils {
private GrpcUtils() {
}
public static String extractServiceName(String service) {
return service.contains(".") ? service.substring(service.lastIndexOf(".") + 1) : service;
}
public static String extractServicePackage(String service) {
return service.contains(".") ? service.substring(0, service.lastIndexOf(".")) : "";
}
public static Object constructGrpcAsyncStub(String packageName, String serviceName, Channel channel, final CallCredentials creds, final CamelContext context) {
return constructGrpcStubClass(packageName, serviceName, GrpcConstants.GRPC_SERVICE_ASYNC_STUB_METHOD, channel, creds, context);
}
public static Object constructGrpcBlockingStub(String packageName, String serviceName, Channel channel, final CallCredentials creds, final CamelContext context) {
return constructGrpcStubClass(packageName, serviceName, GrpcConstants.GRPC_SERVICE_SYNC_STUB_METHOD, channel, creds, context);
}
/**
* Get gRPC stub class instance depends on the invocation style
* newBlockingStub - for sync style
* newStub - for async style
* newFutureStub - for ListenableFuture-style (not implemented yet)
*/
@SuppressWarnings({"rawtypes"})
private static Object constructGrpcStubClass(String packageName, String serviceName, String stubMethod, Channel channel, final CallCredentials creds, final CamelContext context) {
Class[] paramChannel = {Channel.class};
Object grpcStub = null;
String serviceClassName = constructFullClassName(packageName, serviceName + GrpcConstants.GRPC_SERVICE_CLASS_POSTFIX);
try {
Class grpcServiceClass = context.getClassResolver().resolveMandatoryClass(serviceClassName);
Method grpcMethod = ReflectionHelper.findMethod(grpcServiceClass, stubMethod, paramChannel);
if (grpcMethod == null) {
throw new IllegalArgumentException("gRPC service method not found: " + serviceClassName + "." + stubMethod);
}
grpcStub = ObjectHelper.invokeMethod(grpcMethod, grpcServiceClass, channel);
if (creds != null) {
return addClientCallCredentials(grpcStub, creds);
}
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("gRPC service class not found: " + serviceClassName);
}
return grpcStub;
}
@SuppressWarnings("rawtypes")
public static Object addClientCallCredentials(Object grpcStub, final CallCredentials creds) {
Class[] paramCallCreds = {CallCredentials.class};
Object grpcStubWithCreds = null;
Method callCredsMethod = ReflectionHelper.findMethod(grpcStub.getClass(), GrpcConstants.GRPC_SERVICE_STUB_CALL_CREDS_METHOD, paramCallCreds);
grpcStubWithCreds = ObjectHelper.invokeMethod(callCredsMethod, grpcStub, creds);
return grpcStubWithCreds;
}
@SuppressWarnings("rawtypes")
public static Class constructGrpcImplBaseClass(String packageName, String serviceName, final CamelContext context) {
Class grpcServerImpl;
String serverBaseImpl = constructFullClassName(packageName, serviceName + GrpcConstants.GRPC_SERVICE_CLASS_POSTFIX + "$" + serviceName + GrpcConstants.GRPC_SERVER_IMPL_POSTFIX);
try {
grpcServerImpl = context.getClassResolver().resolveMandatoryClass(serverBaseImpl);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("gRPC server base class not found: " + serverBaseImpl);
}
return grpcServerImpl;
}
@SuppressWarnings({"rawtypes", "unchecked"})
public static void invokeAsyncMethod(Object asyncStubClass, String invokeMethod, Object request, StreamObserver responseObserver) {
Class[] paramMethod = null;
Method method = ReflectionHelper.findMethod(asyncStubClass.getClass(), invokeMethod, paramMethod);
if (method == null) {
throw new IllegalArgumentException("gRPC service method not found: " + asyncStubClass.getClass().getName() + "." + invokeMethod);
}
if (method.getReturnType().equals(StreamObserver.class)) {
StreamObserver<Object> requestObserver = (StreamObserver<Object>)ObjectHelper.invokeMethod(method, asyncStubClass, responseObserver);
if (request instanceof List) {
List<Object> requestList = (List<Object>)request;
requestList.forEach((requestItem) -> {
requestObserver.onNext(requestItem);
});
} else {
requestObserver.onNext(request);
}
requestObserver.onCompleted();
} else {
ObjectHelper.invokeMethod(method, asyncStubClass, request, responseObserver);
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
public static StreamObserver<Object> invokeAsyncMethodStreaming(Object asyncStubClass, String invokeMethod, StreamObserver<?> responseObserver) {
Class[] paramMethod = null;
Method method = ReflectionHelper.findMethod(asyncStubClass.getClass(), invokeMethod, paramMethod);
if (method == null) {
throw new IllegalArgumentException("gRPC service method not found: " + asyncStubClass.getClass().getName() + "." + invokeMethod);
}
if (!StreamObserver.class.isAssignableFrom(method.getReturnType())) {
throw new IllegalArgumentException("gRPC service method does not declare an input of type stream (cannot be used in streaming mode): "
+ asyncStubClass.getClass().getName() + "." + invokeMethod);
}
return (StreamObserver<Object>) ObjectHelper.invokeMethod(method, asyncStubClass, responseObserver);
}
@SuppressWarnings({"rawtypes", "unchecked"})
public static Object invokeSyncMethod(Object blockingStubClass, String invokeMethod, Object request) {
Class[] paramMethod = null;
Method method = ReflectionHelper.findMethod(blockingStubClass.getClass(), invokeMethod, paramMethod);
if (method == null) {
throw new IllegalArgumentException("gRPC service method not found: " + blockingStubClass.getClass().getName() + "." + invokeMethod);
}
if (method.getReturnType().equals(Iterator.class)) {
Iterator<Object> responseObjects = (Iterator<Object>)ObjectHelper.invokeMethod(method, blockingStubClass, request);
List<Object> objectList = new ArrayList<Object>();
while (responseObjects.hasNext()) {
objectList.add(responseObjects.next());
}
return objectList;
} else {
return ObjectHelper.invokeMethod(method, blockingStubClass, request);
}
}
/**
* Migrated MixedLower function from the gRPC converting plugin source code
* (https://github.com/grpc/grpc-java/blob/master/compiler/src/java_plugin/cpp/java_generator.cpp)
*
* - decapitalize the first letter
* - remove embedded underscores & capitalize the following letter
*/
public static String convertMethod2CamelCase(final String method) {
StringBuilder sb = new StringBuilder(method.length());
sb.append(method.substring(0, 1).toLowerCase());
Boolean afterUnderscore = false;
for (int i = 1; i < method.length(); i++) {
if (method.charAt(i) == '_') {
afterUnderscore = true;
} else {
sb.append(afterUnderscore ? Character.toUpperCase(method.charAt(i)) : method.charAt(i));
afterUnderscore = false;
}
}
return sb.toString();
}
private static String constructFullClassName(String packageName, String className) {
if (ObjectHelper.isEmpty(packageName)) {
return className;
} else {
return packageName + "." + className;
}
}
}