/*
* 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.
*/packageorg.apache.nifi.processors.grpc;importcom.fasterxml.jackson.core.JsonProcessingException;importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.google.gson.Gson;importcom.google.gson.GsonBuilder;importcom.google.protobuf.ByteString;importio.grpc.CompressorRegistry;importio.grpc.DecompressorRegistry;importio.grpc.ManagedChannel;importio.grpc.netty.NettyChannelBuilder;importio.grpc.stub.StreamObserver;importio.netty.handler.ssl.SslContext;importorg.apache.nifi.annotation.behavior.InputRequirement;importorg.apache.nifi.annotation.behavior.SupportsBatching;importorg.apache.nifi.annotation.behavior.WritesAttribute;importorg.apache.nifi.annotation.behavior.WritesAttributes;importorg.apache.nifi.annotation.documentation.CapabilityDescription;importorg.apache.nifi.annotation.documentation.DeprecationNotice;importorg.apache.nifi.annotation.documentation.Tags;importorg.apache.nifi.annotation.lifecycle.OnScheduled;importorg.apache.nifi.annotation.lifecycle.OnShutdown;importorg.apache.nifi.components.PropertyDescriptor;importorg.apache.nifi.components.ValidationContext;importorg.apache.nifi.components.ValidationResult;importorg.apache.nifi.flowfile.FlowFile;importorg.apache.nifi.logging.ComponentLog;importorg.apache.nifi.processor.*;importorg.apache.nifi.processor.exception.ProcessException;importorg.apache.nifi.processor.util.StandardValidators;importorg.apache.nifi.processors.grpc.gen.edge.v1.*;importorg.apache.nifi.processors.grpc.id.EntityType;importorg.apache.nifi.processors.grpc.ssl.SslContextProvider;importorg.apache.nifi.processors.grpc.util.EdgeUtils;importorg.apache.nifi.ssl.SSLContextService;importorg.apache.nifi.util.StopWatch;importjava.io.InputStream;importjava.net.InetAddress;importjava.net.UnknownHostException;importjava.util.*;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.ConcurrentMap;importjava.util.concurrent.TimeUnit;importjava.util.concurrent.atomic.AtomicReference;importjava.util.concurrent.locks.ReentrantLock;importjava.util.function.Consumer;@SupportsBatching@Tags({"grpc","rpc","client"})@InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)@CapabilityDescription("Sends FlowFiles, optionally with content, to a configurable remote gRPC service endpoint. The remote gRPC service must abide by the service IDL defined in NiFi. "+" gRPC isn't intended to carry large payloads, so this processor should be used only when FlowFile"+" sizes are on the order of megabytes. The default maximum message size is 4MB.")@WritesAttributes({@WritesAttribute(attribute ="invokegrpc.response.code", description ="The response code that is returned (0 = ERROR, 1 = SUCCESS, 2 = RETRY)"),@WritesAttribute(attribute ="invokegrpc.response.body", description ="The response message that is returned"),@WritesAttribute(attribute ="invokegrpc.service.host", description ="The remote gRPC service hostname"),@WritesAttribute(attribute ="invokegrpc.service.port", description ="The remote gRPC service port"),@WritesAttribute(attribute ="invokegrpc.service.cloudKey", description ="The remote gRPC service cloudKey"),@WritesAttribute(attribute ="invokegrpc.service.cloudSecret", description ="The remote gRPC service cloudSecret"),@WritesAttribute(attribute ="invokegrpc.java.exception.class", description ="The Java exception class raised when the processor fails"),@WritesAttribute(attribute ="invokegrpc.java.exception.message", description ="The Java exception message raised when the processor fails"),})@DeprecationNotice(reason ="No planned alternatives to be offered. Use custom processors instead.")publicclassInvokeGRPCextendsAbstractProcessor{publicstaticfinalStringRESPONSE_CODE="invokegrpc.response.code";publicstaticfinalStringRESPONSE_BODY="invokegrpc.response.body";publicstaticfinalStringSERVICE_HOST="invokegrpc.service.host";publicstaticfinalStringSERVICE_PORT="invokegrpc.service.port";publicstaticfinalStringROUTING_CLOUD_KEY="invokegrpc.service.cloudKey";publicstaticfinalStringROUTING_CLOUD_SECRET="invokegrpc.service.cloudSecret";publicstaticfinalStringEXCEPTION_CLASS="invokegrpc.java.exception.class";publicstaticfinalStringEXCEPTION_MESSAGE="invokegrpc.java.exception.message";// propertiespublicstaticfinalPropertyDescriptorPROP_SERVICE_HOST=newPropertyDescriptor.Builder().name("Remote gRPC service hostname").description("Remote host which will be connected to").required(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();publicstaticfinalPropertyDescriptorPROP_SERVICE_PORT=newPropertyDescriptor.Builder().name("Remote gRPC service port").description("Remote port which will be connected to").required(true).addValidator(StandardValidators.PORT_VALIDATOR).build();publicstaticfinalPropertyDescriptorPROP_ROUTING_CLOUD_KEY=newPropertyDescriptor.Builder().name("Remote gRPC service key").description("Remote key which will be connected to").required(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();publicstaticfinalPropertyDescriptorPROP_ROUTING_CLOUD_SECRET=newPropertyDescriptor.Builder().name("Remote gRPC service secret").description("secret which will be connected to").required(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();publicstaticfinalPropertyDescriptorPROP_MAX_MESSAGE_SIZE=newPropertyDescriptor.Builder().name("Max Message Size").description("The maximum size of FlowFiles that this processor will allow to be received."+" The default is 4MB. If FlowFiles exceed this size, you should consider using another transport mechanism"+" as gRPC isn't designed for heavy payloads.").defaultValue("4MB").required(false).addValidator(StandardValidators.DATA_SIZE_VALIDATOR).build();publicstaticfinalPropertyDescriptorPROP_USE_SECURE=newPropertyDescriptor.Builder().name("Use SSL/TLS").displayName("Use TLS").description("Whether or not to use TLS to send the contents of the gRPC messages.").required(false).defaultValue("false").allowableValues("true","false").build();publicstaticfinalPropertyDescriptorPROP_SSL_CONTEXT_SERVICE=newPropertyDescriptor.Builder().name("SSL Context Service").description("The SSL Context Service used to provide client certificate information for TLS (https) connections.").required(false).identifiesControllerService(SSLContextService.class).dependsOn(PROP_USE_SECURE,"true").build();publicstaticfinalPropertyDescriptorPROP_SEND_CONTENT=newPropertyDescriptor.Builder().name("Send FlowFile Content").description("Whether or not to include the FlowFile content in the FlowFileRequest to the gRPC service.").required(false).defaultValue("true").allowableValues("true","false").build();publicstaticfinalPropertyDescriptorPROP_PENALIZE_NO_RETRY=newPropertyDescriptor.Builder().name("Penalize on \"No Retry\"").description("Enabling this property will penalize FlowFiles that are routed to the \"No Retry\" relationship.").required(false).defaultValue("false").allowableValues("true","false").build();publicstaticfinalPropertyDescriptorPROP_OUTPUT_RESPONSE_REGARDLESS=newPropertyDescriptor.Builder().name("Always Output Response").description("Will force a response FlowFile to be generated and routed to the 'Response' relationship regardless of what the server status code received is "+"or if the processor is configured to put the server response body in the request attribute. In the later configuration a request FlowFile with the "+"response body in the attribute and a typical response FlowFile will be emitted to their respective relationships.").required(false).defaultValue("false").allowableValues("true","false").build();publicstaticfinalList<PropertyDescriptor>PROPERTIES=Collections.unmodifiableList(Arrays.asList(PROP_SERVICE_HOST,PROP_SERVICE_PORT,PROP_ROUTING_CLOUD_KEY,PROP_ROUTING_CLOUD_SECRET,PROP_MAX_MESSAGE_SIZE,PROP_USE_SECURE,PROP_SSL_CONTEXT_SERVICE,PROP_SEND_CONTENT,PROP_OUTPUT_RESPONSE_REGARDLESS,PROP_PENALIZE_NO_RETRY));// relationshipspublicstaticfinalRelationshipREL_SUCCESS_REQ=newRelationship.Builder().name("Success").description("The original FlowFile will be routed upon success. It will have new attributes detailing the "+"success of the request.").build();/*public static final Relationship REL_RESPONSE = new Relationship.Builder()
.name("Response")
.description("A Response FlowFile will be routed upon success. If the 'Output Response Regardless' property "
+ "is true then the response will be sent to this relationship regardless of the status code received.")
.build();
public static final Relationship REL_RETRY = new Relationship.Builder()
.name("Retry")
.description("The original FlowFile will be routed on any status code that can be retried. It will have new "
+ "attributes detailing the request.")
.build();*//*public static final Relationship REL_NO_RETRY = new Relationship.Builder()
.name("No Retry")
.description("The original FlowFile will be routed on any status code that should NOT be retried. "
+ "It will have new attributes detailing the request.")
.build();*/publicstaticfinalRelationshipREL_FAILURE=newRelationship.Builder().name("Failure").description("The original FlowFile will be routed on any type of connection failure, timeout or general exception. "+"It will have new attributes detailing the request.").build();publicstaticfinalSet<Relationship>RELATIONSHIPS=Collections.unmodifiableSet(newHashSet<>(Arrays.asList(REL_SUCCESS_REQ,/*REL_NO_RETRY,
REL_RESPONSE,
REL_RETRY,*/REL_FAILURE)));privatestaticfinalStringUSER_AGENT_PREFIX="NiFi_invokeGRPC";// NOTE: you may need to add the sources generated after running `maven clean compile` to your IDE// configured source directories. Otherwise, the classes generated when the proto is compiled won't// be accessible from here. For IntelliJ, open this module's settings and mark the following as source directories://// * target/generated-sources/protobuf/grpc-java// * target/generated-sources/protobuf/java//private final AtomicReference<FlowFileServiceGrpc.FlowFileServiceBlockingStub> blockingStubReference = new AtomicReference<>();privatestaticfinalAtomicReference<EdgeRpcServiceGrpc.EdgeRpcServiceStub> blockingStubReference =newAtomicReference<>();privatestaticfinalAtomicReference<ManagedChannel> channelReference =newAtomicReference<>();privatestaticStreamObserver<RequestMsg> inputStream;privatestaticfinalReentrantLock uplinkMsgLock =newReentrantLock();privatefinalstaticConcurrentMap<Integer,Boolean> pendingMsgsMap =newConcurrentHashMap<>();@OverrideprotectedList<PropertyDescriptor>getSupportedPropertyDescriptors(){returnPROPERTIES;}@OverridepublicSet<Relationship>getRelationships(){returnRELATIONSHIPS;}@OverrideprotectedCollection<ValidationResult>customValidate(ValidationContext context){List<ValidationResult> results =newArrayList<>(super.customValidate(context));finalboolean useSecure = context.getProperty(PROP_USE_SECURE).asBoolean();finalboolean sslContextServiceConfigured = context.getProperty(PROP_SSL_CONTEXT_SERVICE).isSet();if(useSecure &&!sslContextServiceConfigured){
results.add(newValidationResult.Builder().subject(PROP_SSL_CONTEXT_SERVICE.getDisplayName()).valid(false).explanation(String.format("'%s' must be configured when '%s' is true",PROP_SSL_CONTEXT_SERVICE.getDisplayName(),PROP_USE_SECURE.getDisplayName())).build());}return results;}/**
* Whenever this processor is triggered, we need to construct a client in order to communicate
* with the configured gRPC service.
*
* @param context the processor context
*/@OnScheduledpublicvoidinitializeClient(finalProcessContext context){connect(context);}privatestaticStreamObserver<ResponseMsg>initOutputStream(String edgeKey,Consumer<UplinkResponseMsg> onUplinkResponse,Consumer<Exception> onError){returnnewStreamObserver<ResponseMsg>(){@OverridepublicvoidonNext(ResponseMsg responseMsg){if(responseMsg.hasConnectResponseMsg()){ConnectResponseMsg connectResponseMsg = responseMsg.getConnectResponseMsg();System.out.println("收到服务端连接回复消息"+ connectResponseMsg);}elseif(responseMsg.hasUplinkResponseMsg()){System.out.println("收到服务端响应消息"+ responseMsg.getUplinkResponseMsg());
onUplinkResponse.accept(responseMsg.getUplinkResponseMsg());}}@OverridepublicvoidonError(Throwable t){
onError.accept(newRuntimeException(t));System.out.println("断开了"+ edgeKey);}@OverridepublicvoidonCompleted(){release();System.out.println("[{}] Stream was closed and completed successfully!"+ edgeKey);}};}protectedstaticvoidrelease(){
channelReference.set(null);
blockingStubReference.set(null);
inputStream =null;
pendingMsgsMap.clear();}protectedbooleanisConnected(){return(inputStream !=null&& blockingStubReference !=null&& channelReference !=null);}privatevoidonError(finalException e){release();}privatevoidonUplinkResponse(UplinkResponseMsg msg){try{if(msg.getSuccess()){
pendingMsgsMap.put(msg.getUplinkMsgId(),true);getLogger().info("服务端响应处理了消息---success"+ msg);}else{
pendingMsgsMap.put(msg.getUplinkMsgId(),false);getLogger().info("服务端响应处理了消息---failure"+ msg.getErrorMsg());}}catch(Exception e){
e.printStackTrace();}}/**
* Perform cleanup prior to JVM shutdown
*
* @param context the processor context
* @throws InterruptedException if there's an issue cleaning up
*/@OnShutdownpublicvoidshutdown(finalProcessContext context)throwsInterruptedException{// close the channel/*final ManagedChannel channel = channelReference.get();
if (channel != null) {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}*/disconnect();}publicvoidconnect(finalProcessContext context){
channelReference.set(null);
blockingStubReference.set(null);finalComponentLog logger =getLogger();finalString host = context.getProperty(PROP_SERVICE_HOST).getValue();finalint port = context.getProperty(PROP_SERVICE_PORT).asInteger();//7070;finalString routingKey = context.getProperty(PROP_ROUTING_CLOUD_KEY).getValue();//"key55555";finalString routingSecret = context.getProperty(PROP_ROUTING_CLOUD_SECRET).getValue();//"s55555";finalint maxMessageSize = context.getProperty(PROP_MAX_MESSAGE_SIZE).asDataSize(DataUnit.B).intValue();String userAgent =USER_AGENT_PREFIX;try{
userAgent +="_"+InetAddress.getLocalHost().getHostName();}catch(finalUnknownHostException e){
logger.warn("Unable to determine local hostname. Defaulting gRPC user agent to {}.",newObject[]{USER_AGENT_PREFIX}, e);}finalNettyChannelBuilder nettyChannelBuilder =NettyChannelBuilder.forAddress(host, port)// supports both gzip and plaintext, but will compress by default..compressorRegistry(CompressorRegistry.getDefaultInstance()).decompressorRegistry(DecompressorRegistry.getDefaultInstance()).maxInboundMessageSize(maxMessageSize).userAgent(userAgent);// configure whether or not we're using secure commsfinalboolean useSecure = context.getProperty(PROP_USE_SECURE).asBoolean();finalSSLContextService sslContextService = context.getProperty(PROP_SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);if(useSecure){finalSslContext clientSslContext =SslContextProvider.getSslContext(sslContextService,true);
nettyChannelBuilder.sslContext(clientSslContext);}else{
nettyChannelBuilder.usePlaintext();}finalManagedChannel channel = nettyChannelBuilder.build();//EdgeRpcServiceGrpc.EdgeRpcServiceBlockingStub stub = EdgeRpcServiceGrpc.newBlockingStub(channel);EdgeRpcServiceGrpc.EdgeRpcServiceStub stub =EdgeRpcServiceGrpc.newStub(channel);
inputStream = stub.withCompression("gzip").handleMsgs(initOutputStream(routingKey,this::onUplinkResponse,this::onError));
inputStream.onNext(RequestMsg.newBuilder().setMsgType(RequestMsgType.CONNECT_RPC_MESSAGE).setConnectRequestMsg(ConnectRequestMsg.newBuilder().setEdgeRoutingKey(routingKey).setEdgeSecret(routingSecret).setEdgeVersion(EdgeVersion.V_3_6_0).setMaxInboundMessageSize(419820).build()).build());//final FlowFileServiceGrpc.FlowFileServiceBlockingStub blockingStub = FlowFileServiceGrpc.newBlockingStub(channel);
channelReference.set(channel);
blockingStubReference.set(stub);}publicvoiddisconnect(){finalComponentLog logger =getLogger();if(inputStream !=null){try{
inputStream.onCompleted();}catch(Exception e){
logger.error("Exception during onCompleted", e);}}finalManagedChannel channel = channelReference.get();if(channel !=null){
channel.shutdown();int attempt =0;do{try{
channel.awaitTermination(5,TimeUnit.SECONDS);}catch(Exception e){
logger.error("Channel await termination was interrupted", e);}if(attempt >5){
logger.warn("We had reached maximum of termination attempts. Force closing channel");try{
channel.shutdownNow();}catch(Exception e){
logger.error("Exception during shutdownNow", e);}break;}
attempt++;}while(!channel.isTerminated());}release();}@OverridepublicvoidonTrigger(finalProcessContext context,finalProcessSession session)throwsProcessException{FlowFile fileToProcess =null;if(context.hasIncomingConnection()){
fileToProcess = session.get();// If we have no FlowFile, and all incoming connections are self-loops then we can continue on.// However, if we have no FlowFile and we have connections coming from other Processors, then// we know that we should run only if we have a FlowFile.if(fileToProcess ==null&& context.hasNonLoopConnection()){return;}}if(!isConnected()){synchronized(this){if(!isConnected()){connect(context);}}}finalComponentLog logger =getLogger();finalEdgeRpcServiceGrpc.EdgeRpcServiceStub blockingStub = blockingStubReference.get();finalString host = context.getProperty(PROP_SERVICE_HOST).getValue();finalString port = context.getProperty(PROP_SERVICE_PORT).getValue();
fileToProcess = session.putAttribute(fileToProcess,SERVICE_HOST, host);
fileToProcess = session.putAttribute(fileToProcess,SERVICE_PORT, port);FlowFile responseFlowFile =null;try{finalorg.apache.nifi.processors.grpc.FlowFileRequest.Builder requestBuilder =org.apache.nifi.processors.grpc.FlowFileRequest.newBuilder().setId(fileToProcess.getId()).putAllAttributes(fileToProcess.getAttributes());// if the processor is configured to send the content, turn the content into bytes// and add it to the request.finalboolean sendContent = context.getProperty(PROP_SEND_CONTENT).asBoolean();String content ="";if(sendContent){try(finalInputStream contents = session.read(fileToProcess)){
requestBuilder.setContent(ByteString.readFrom(contents));
content = requestBuilder.getContent().toStringUtf8();getLogger().info("send data content "+ content);}// emit provenance event
session.getProvenanceReporter().send(fileToProcess,getRemote(host, port),true);}finalorg.apache.nifi.processors.grpc.FlowFileRequest flowFileRequest = requestBuilder.build();logRequest(logger, host, port, flowFileRequest);//final FlowFileReply flowFileReply = blockingStub.send(flowFileRequest);//logReply(logger, host, port, flowFileReply);org.apache.nifi.processors.grpc.FlowFileReply.ResponseCode responseCode =org.apache.nifi.processors.grpc.FlowFileReply.ResponseCode.SUCCESS;//flowFileReply.getResponseCode();finalString body ="SUCCESS";//flowFileReply.getBody();
fileToProcess = session.putAttribute(fileToProcess,RESPONSE_CODE,String.valueOf(responseCode));
fileToProcess = session.putAttribute(fileToProcess,RESPONSE_BODY, body);//responseFlowFile = session.create(fileToProcess);ObjectMapper mapper =newObjectMapper();Map jsonData =newHashMap();Map newContent =newHashMap();
newContent.put("collectData",content);
jsonData.put("data", mapper.readTree(mapper.writeValueAsString(newContent)));Gson gson =newGsonBuilder().create();
content = gson.toJson(jsonData);JsonNode actualObj = mapper.readTree(content);UplinkMsg msg =buildUpLinkMsg(actualObj);RequestMsg requestMsg =RequestMsg.newBuilder().setMsgType(RequestMsgType.UPLINK_RPC_MESSAGE).setUplinkMsg(msg).build();finalStopWatch stopWatch =newStopWatch(true);
uplinkMsgLock.lock();try{
inputStream.onNext(requestMsg);}catch(Exception e){
logger.error("GRPC send msg to server {} due to exception: {}", host +":"+ port, e);
logger.info("GRPC try reconnect to server {} ", host +":"+ port);release();
logger.error("Routing to {} due to exception: {}",newObject[]{REL_FAILURE.getName(), e}, e);
fileToProcess = session.penalize(fileToProcess);
fileToProcess = session.putAttribute(fileToProcess,EXCEPTION_CLASS, e.getClass().getName());
fileToProcess = session.putAttribute(fileToProcess,EXCEPTION_MESSAGE, e.getMessage());// transfer original to failure
session.transfer(fileToProcess,REL_FAILURE);}finally{
uplinkMsgLock.unlock();}
pendingMsgsMap.put(msg.getUplinkMsgId(),false);Thread.sleep(500);if(pendingMsgsMap.get(msg.getUplinkMsgId())){
responseCode =org.apache.nifi.processors.grpc.FlowFileReply.ResponseCode.SUCCESS;}else{
responseCode =org.apache.nifi.processors.grpc.FlowFileReply.ResponseCode.ERROR;}
session.getProvenanceReporter().send(fileToProcess,getRemote(host, port), stopWatch.getElapsed(TimeUnit.MILLISECONDS));route(fileToProcess, responseFlowFile, session, context, responseCode);
pendingMsgsMap.remove(msg.getUplinkMsgId());}catch(finalException e){release();// penalize or yieldif(fileToProcess !=null){
logger.error("Routing to {} due to exception: {}",newObject[]{REL_FAILURE.getName(), e}, e);
fileToProcess = session.penalize(fileToProcess);
fileToProcess = session.putAttribute(fileToProcess,EXCEPTION_CLASS, e.getClass().getName());
fileToProcess = session.putAttribute(fileToProcess,EXCEPTION_MESSAGE, e.getMessage());// transfer original to failure
session.transfer(fileToProcess,REL_FAILURE);}else{
logger.error("Yielding processor due to exception encountered as a source processor: {}", e);
context.yield();}// cleanup/*try {
if (responseFlowFile != null) {
session.remove(responseFlowFile);
}
} catch (final Exception e1) {
logger.error("Could not cleanup response flowfile due to exception: {}", new Object[]{e1}, e1);
}*/}}privateUplinkMsgbuildUpLinkMsg(JsonNode jsonNode)throwsJsonProcessingException{EntityType entityType =EntityType.DEVICE;UUID entityId =UUID.randomUUID();EntityDataProto entityDataProto =EdgeUtils.convertTelemetryEventToEntityDataProto(entityId, entityType,jsonNode);returnUplinkMsg.newBuilder().setUplinkMsgId(EdgeUtils.nextPositiveInt()).addEntityData(entityDataProto).build();}/**
* Route the {@link FlowFile} request and response appropriately, depending on the gRPC service
* response code.
*
* @param request the flowfile request
* @param response the flowfile response
* @param session the processor session
* @param context the processor context
* @param responseCode the gRPC service response code
*/privatevoidroute(FlowFile request,FlowFile response,finalProcessSession session,finalProcessContext context,finalorg.apache.nifi.processors.grpc.FlowFileReply.ResponseCode responseCode){/*boolean responseSent = false;
if (context.getProperty(PROP_OUTPUT_RESPONSE_REGARDLESS).asBoolean()) {
session.transfer(response, REL_RESPONSE);
responseSent = true;
}*/switch(responseCode){// if the rpc failed, transfer flowfile to no retry relationship and penalize flowfile// if the rpc succeeded, transfer the request and response flowfilescaseSUCCESS:
session.transfer(request,REL_SUCCESS_REQ);getLogger().info("Flowfile {} transfer to {}",request.getId(),REL_SUCCESS_REQ);/*if (!responseSent) {
session.transfer(response, REL_RESPONSE);
}*/break;// if the gRPC service responded requesting a retry, then penalize the request and// transfer it to the retry relationship. The flowfile contains attributes detailing this// rpc request./*case RETRY:
request = session.penalize(request);
session.transfer(request, REL_RETRY);
// if we haven't sent the response by this point, clean it up.
if (!responseSent) {
session.remove(response);
}
break;*/caseERROR:
session.transfer(request,REL_FAILURE);getLogger().info("Flowfile {} transfer to {}",request.getId(),REL_FAILURE);/*if (!responseSent) {
session.transfer(response, REL_RESPONSE);
}*/break;caseUNRECOGNIZED:// unrecognized response code returned from gRPC service
session.transfer(request,REL_FAILURE);getLogger().info("Flowfile {} transfer to {}",request.getId(),REL_FAILURE);/*if (!responseSent) {
session.transfer(response, REL_RESPONSE);
}*/break;default:finalboolean penalize = context.getProperty(PROP_PENALIZE_NO_RETRY).asBoolean();if(penalize){
request = session.penalize(request);}
session.transfer(request,REL_FAILURE);getLogger().info("Flowfile {} transfer to {}",request.getId(),REL_FAILURE);// if we haven't sent the response by this point, clean it up./*if (!responseSent) {
session.remove(response);
}*/break;}}privateStringgetRemote(finalString host,finalString port){return host +":"+ port;}privatevoidlogRequest(finalComponentLog logger,finalString host,finalString port,finalorg.apache.nifi.processors.grpc.FlowFileRequest flowFileRequest){
logger.debug("\nRequest to remote service:\n\t{}\n{}",getRemote(host, port), flowFileRequest.toString());}privatevoidlogReply(finalComponentLog logger,finalString host,finalString port,finalorg.apache.nifi.processors.grpc.FlowFileReply flowFileReply){
logger.debug("\nResponse from remote service:\n\t{}\n{}",getRemote(host, port), flowFileReply.toString());}}