GH-750 Add support for pluggable protobufs

This initial support adds plugin extension to support CloudEvent proto as well as the example
Additional plugins could be provided in the same ay as CloudEvent plugin extension

Resolves #750
This commit is contained in:
Oleg Zhurakousky
2021-10-11 14:03:24 +02:00
parent 346ff53539
commit 7fc755e157
41 changed files with 2659 additions and 312 deletions

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2021-2021 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 org.springframework.cloud.function.grpc.ce;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.function.grpc.MessageHandlingHelper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.grpc.BindableService;
/**
*
* @author Oleg Zhurakousky
*
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.function.grpc.server", havingValue = "true", matchIfMissing = true)
public class CloudEventGrpcAutoConfiguration {
@Bean
public BindableService cloudEventMessageHandler(MessageHandlingHelper helper) {
return new CloudEventHandler(helper);
}
@Bean
@ConditionalOnMissingBean
public CloudEventMessageConverter cloudEventMessageConverter() {
return new CloudEventMessageConverter();
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2021-2021 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.
*/
/*
* Copyright 2021-2021 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.
*/
/*
* Copyright 2021-2021 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 org.springframework.cloud.function.grpc.ce;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.function.grpc.MessageHandlingHelper;
import io.cloudevents.v1.CloudEventServiceGrpc.CloudEventServiceImplBase;
import io.cloudevents.v1.proto.CloudEvent;
import io.grpc.stub.StreamObserver;
/**
*
* @author Oleg Zhurakousky
* @since 3.2
*
*/
@SuppressWarnings("rawtypes")
class CloudEventHandler extends CloudEventServiceImplBase {
private Log logger = LogFactory.getLog(CloudEventHandler.class);
private final MessageHandlingHelper helper;
public CloudEventHandler(MessageHandlingHelper helper) {
this.helper = helper;
}
@SuppressWarnings("unchecked")
@Override
public void requestReply(CloudEvent request, StreamObserver<CloudEvent> responseObserver) {
this.helper.requestReply(request, responseObserver);
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2021-2021 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 org.springframework.cloud.function.grpc.ce;
import java.util.Map.Entry;
import org.springframework.cloud.function.cloudevent.CloudEventMessageUtils;
import org.springframework.cloud.function.grpc.AbstractGrpcMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import com.google.protobuf.ByteString;
import com.google.protobuf.GeneratedMessageV3;
import io.cloudevents.v1.proto.CloudEvent;
import io.cloudevents.v1.proto.CloudEvent.Builder;
import io.cloudevents.v1.proto.CloudEvent.CloudEventAttributeValue;
import io.cloudevents.v1.proto.CloudEvent.CloudEventAttributeValue.AttrCase;
/**
*
* @author Oleg Zhurakousky
*
*/
public class CloudEventMessageConverter extends AbstractGrpcMessageConverter<CloudEvent> {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
protected Message<byte[]> doToSpringMessage(CloudEvent cloudEvent) {
MessageBuilder builder = MessageBuilder.withPayload(cloudEvent.getTextData());
builder.setHeader(CloudEventMessageUtils.TYPE, cloudEvent.getType());
builder.setHeader(CloudEventMessageUtils.SOURCE, cloudEvent.getSource());
builder.setHeader(CloudEventMessageUtils.ID, cloudEvent.getId());
builder.setHeader(CloudEventMessageUtils.SPECVERSION, cloudEvent.getId());
for (Entry<String, CloudEventAttributeValue> attributeEntry : cloudEvent.getAttributesMap().entrySet()) {
AttrCase attrCase = attributeEntry.getValue().getAttrCase();
if (attrCase.equals(AttrCase.CE_BOOLEAN)) {
builder.setHeader(attributeEntry.getKey(), attributeEntry.getValue().getCeBoolean());
}
else if (attrCase.equals(AttrCase.CE_BYTES)) {
builder.setHeader(attributeEntry.getKey(), attributeEntry.getValue().getCeBytes());
}
else if (attrCase.equals(AttrCase.CE_INTEGER)) {
builder.setHeader(attributeEntry.getKey(), attributeEntry.getValue().getCeInteger());
}
else if (attrCase.equals(AttrCase.CE_STRING)) {
builder.setHeader(attributeEntry.getKey(), attributeEntry.getValue().getCeString());
}
else if (attrCase.equals(AttrCase.CE_TIMESTAMP)) {
builder.setHeader(attributeEntry.getKey(), attributeEntry.getValue().getCeTimestamp());
}
else if (attrCase.equals(AttrCase.CE_URI)) {
builder.setHeader(attributeEntry.getKey(), attributeEntry.getValue().getCeUri());
}
else if (attrCase.equals(AttrCase.CE_URI_REF)) {
builder.setHeader(attributeEntry.getKey(), attributeEntry.getValue().getCeUriRef());
}
else {
throw new IllegalStateException("Unknown type for attribute " + attributeEntry.getKey());
}
}
return builder.build();
}
@Override
protected CloudEvent doFromSpringMessage(Message<byte[]> springMessage) {
Builder builder = CloudEvent.newBuilder()
.setTextDataBytes(ByteString.copyFrom(springMessage.getPayload()))
.setType(CloudEventMessageUtils.getType(springMessage))
.setSource(CloudEventMessageUtils.getSource(springMessage).toString())
.setId(CloudEventMessageUtils.getId(springMessage))
.setSpecVersion(CloudEventMessageUtils.getSpecVersion(springMessage));
for (Entry<String, Object> entry : springMessage.getHeaders().entrySet()) {
builder.putAttributes(entry.getKey(), CloudEventAttributeValue.newBuilder().setCeString(entry.getValue().toString()).build());
}
return builder.build();
}
@Override
protected boolean supports(Class<? extends GeneratedMessageV3> grpcClass) {
return grpcClass.isAssignableFrom(CloudEvent.class);
}
}

View File

@@ -0,0 +1,49 @@
syntax = "proto3";
package io.cloudevents.v1;
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
option go_package = "cloudevents.io/genproto/v1";
option java_package = "io.cloudevents.v1.proto";
option java_multiple_files = true;
message CloudEvent {
// -- CloudEvent Context Attributes
// Required Attributes
string id = 1;
string source = 2; // URI-reference
string spec_version = 3;
string type = 4;
// Optional & Extension Attributes
map<string, CloudEventAttributeValue> attributes = 5;
// -- CloudEvent Data (Bytes, Text, or Proto)
oneof data {
bytes binary_data = 6;
string text_data = 7;
google.protobuf.Any proto_data = 8;
}
/**
* The CloudEvent specification defines
* seven attribute value types...
*/
message CloudEventAttributeValue {
oneof attr {
bool ce_boolean = 1;
int32 ce_integer = 2;
string ce_string = 3;
bytes ce_bytes = 4;
string ce_uri = 5;
string ce_uri_ref = 6;
google.protobuf.Timestamp ce_timestamp = 7;
}
}
}

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package io.cloudevents.v1;
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import "CloudEvent.proto";
service CloudEventService {
rpc biStream(stream io.cloudevents.v1.CloudEvent) returns (stream io.cloudevents.v1.CloudEvent);
rpc clientStream(stream io.cloudevents.v1.CloudEvent) returns (io.cloudevents.v1.CloudEvent);
rpc serverStream(io.cloudevents.v1.CloudEvent) returns (stream io.cloudevents.v1.CloudEvent);
rpc requestReply(io.cloudevents.v1.CloudEvent) returns (io.cloudevents.v1.CloudEvent);
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.function.grpc.ce.CloudEventGrpcAutoConfiguration

View File

@@ -0,0 +1,13 @@
package org.springframework.cloud.grpc.ce;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringCloudFunctionGrpcCloudeventApplicationTests {
// @Test
// void contextLoads() {
// }
}