From 113c3eb294ce67323d9fc1b6099118a56697d11f Mon Sep 17 00:00:00 2001
From: OZGCloud <ozgcloud@mgm-tp.com>
Date: Thu, 17 Aug 2023 14:47:48 +0200
Subject: [PATCH] build and add call context

---
 .../common/callcontext/CallContext.java       | 24 ++++++
 .../DefaultOzgCloudCallContextProvider.java   | 33 +++++++
 ...gCloudCallContextAttachingInterceptor.java | 86 +++++++++++++++++++
 .../OzgCloudCallContextProvider.java          |  6 ++
 ...e.java => GrpcOzgCloudVorgangService.java} |  2 +-
 ...va => GrpcOzgCloudVorgangServiceTest.java} |  4 +-
 .../de/ozgcloud/apilib/demo/DemoRunner.java   |  2 +-
 .../OzgCloudClientAutoConfiguration.java      | 18 +++-
 8 files changed, 168 insertions(+), 7 deletions(-)
 create mode 100644 api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/CallContext.java
 create mode 100644 api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/DefaultOzgCloudCallContextProvider.java
 create mode 100644 api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/OzgCloudCallContextAttachingInterceptor.java
 create mode 100644 api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/OzgCloudCallContextProvider.java
 rename api-lib-core/src/main/java/de/ozgcloud/apilib/vorgang/grpc/{GrpcVorgangService.java => GrpcOzgCloudVorgangService.java} (96%)
 rename api-lib-core/src/test/java/de/ozgcloud/apilib/vorgang/grpc/{GrpcVorgangServiceTest.java => GrpcOzgCloudVorgangServiceTest.java} (97%)

diff --git a/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/CallContext.java b/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/CallContext.java
new file mode 100644
index 0000000..449032b
--- /dev/null
+++ b/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/CallContext.java
@@ -0,0 +1,24 @@
+package de.ozgcloud.apilib.common.callcontext;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import de.ozgcloud.apilib.user.OzgCloudUserId;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Singular;
+
+@Builder
+@Getter
+public class CallContext {
+
+	private String clientName;
+	private OzgCloudUserId userId;
+
+	@Builder.Default
+	private String requestId = UUID.randomUUID().toString();
+	@Builder.Default
+	private boolean accessLimited = false;
+	@Singular
+	private Collection<String> accessLimitedToOrgaIds;
+}
diff --git a/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/DefaultOzgCloudCallContextProvider.java b/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/DefaultOzgCloudCallContextProvider.java
new file mode 100644
index 0000000..2528822
--- /dev/null
+++ b/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/DefaultOzgCloudCallContextProvider.java
@@ -0,0 +1,33 @@
+package de.ozgcloud.apilib.common.callcontext;
+
+import java.util.Optional;
+import java.util.UUID;
+import java.util.logging.Level;
+
+import org.springframework.context.ApplicationContext;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.java.Log;
+
+@Log
+@RequiredArgsConstructor
+public class DefaultOzgCloudCallContextProvider implements OzgCloudCallContextProvider {
+
+	private final ApplicationContext context;
+
+	@Override
+	public CallContext provideContext() {
+		return CallContext.builder()
+				.clientName(getClientName())
+				.requestId(UUID.randomUUID().toString())
+				.build();
+	}
+
+	private String getClientName() {
+		return Optional.ofNullable(context.getId()).orElseGet(() -> {
+			LOG.log(Level.WARNING, "No Client name given. Please configure 'spring.application.name'.");
+			return "unkown_client";
+		});
+
+	}
+}
diff --git a/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/OzgCloudCallContextAttachingInterceptor.java b/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/OzgCloudCallContextAttachingInterceptor.java
new file mode 100644
index 0000000..50a5ddc
--- /dev/null
+++ b/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/OzgCloudCallContextAttachingInterceptor.java
@@ -0,0 +1,86 @@
+package de.ozgcloud.apilib.common.callcontext;
+
+import static de.itvsh.kop.common.grpc.GrpcUtil.*;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.ozgcloud.apilib.user.OzgCloudUserId;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import lombok.RequiredArgsConstructor;
+import net.devh.boot.grpc.client.interceptor.GrpcGlobalClientInterceptor;
+
+@GrpcGlobalClientInterceptor
+@RequiredArgsConstructor
+public class OzgCloudCallContextAttachingInterceptor implements ClientInterceptor {
+
+	static final String KEY_USER_ID = "USER_ID-bin";
+	static final String KEY_CLIENT_NAME = "CLIENT_NAME-bin";
+	static final String KEY_REQUEST_ID = "REQUEST_ID-bin";
+	static final String KEY_ACCESS_LIMITED_ORGAID = "ACCESS_LIMITED_TO_ORGANISATORISCHEEINHEITENID-bin";
+	static final String KEY_ACCESS_LIMITED = "ACCESS_LIMITED-bin";
+
+	private final OzgCloudCallContextProvider callContextProvider;
+
+	@Override
+	public <A, B> ClientCall<A, B> interceptCall(MethodDescriptor<A, B> method, CallOptions callOptions, Channel next) {
+		return new CallContextAttachingClientCall<>(next.newCall(method, callOptions));
+	}
+
+	final class CallContextAttachingClientCall<A, B> extends SimpleForwardingClientCall<A, B> {
+
+		protected CallContextAttachingClientCall(ClientCall<A, B> delegate) {
+			super(delegate);
+		}
+
+		@Override
+		public void start(Listener<B> responseListener, Metadata headers) {
+			headers.merge(buildCallContextMetadata());
+			super.start(responseListener, headers);
+		}
+
+		private Metadata buildCallContextMetadata() {
+			var ctxt = callContextProvider.provideContext();
+			var metadata = new Metadata();
+
+			addClientName(ctxt, metadata);
+			addRequestId(ctxt, metadata);
+			Optional.ofNullable(ctxt.getUserId())
+					.map(OzgCloudUserId::toString)
+					.ifPresent(userId -> addToMetadata(metadata, KEY_USER_ID, userId));
+			Optional.ofNullable(ctxt.getAccessLimitedToOrgaIds())
+					.ifPresent(ids -> ids.stream().forEach(id -> addToMetadata(metadata, KEY_ACCESS_LIMITED_ORGAID, id)));
+			Optional.ofNullable(ctxt.isAccessLimited()).map(Boolean::valueOf)
+					.ifPresent(limited -> addToMetadata(metadata, KEY_ACCESS_LIMITED, limited.toString()));
+
+			return metadata;
+		}
+
+		private void addRequestId(CallContext ctxt, Metadata metadata) {
+			Optional.ofNullable(ctxt.getRequestId()).ifPresentOrElse(reqId -> metadata.put(createKeyOf(KEY_REQUEST_ID), reqId.getBytes()),
+					() -> addToMetadata(metadata, KEY_REQUEST_ID, UUID.randomUUID().toString()));
+		}
+
+		private void addClientName(CallContext ctxt, Metadata metadata) {
+			Optional.ofNullable(ctxt.getClientName())
+					.filter(StringUtils::isNoneBlank)
+					.ifPresentOrElse(clientName -> addToMetadata(metadata, KEY_CLIENT_NAME, clientName),
+							() -> {
+								throw new IllegalStateException("CLIENT_NAME must not be blank");
+							});
+		}
+
+		private void addToMetadata(Metadata metadata, String key, String value) {
+			metadata.put(createKeyOf(key), value.getBytes());
+		}
+
+	}
+}
diff --git a/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/OzgCloudCallContextProvider.java b/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/OzgCloudCallContextProvider.java
new file mode 100644
index 0000000..2bd4a70
--- /dev/null
+++ b/api-lib-core/src/main/java/de/ozgcloud/apilib/common/callcontext/OzgCloudCallContextProvider.java
@@ -0,0 +1,6 @@
+package de.ozgcloud.apilib.common.callcontext;
+
+public interface OzgCloudCallContextProvider {
+
+	public CallContext provideContext();
+}
diff --git a/api-lib-core/src/main/java/de/ozgcloud/apilib/vorgang/grpc/GrpcVorgangService.java b/api-lib-core/src/main/java/de/ozgcloud/apilib/vorgang/grpc/GrpcOzgCloudVorgangService.java
similarity index 96%
rename from api-lib-core/src/main/java/de/ozgcloud/apilib/vorgang/grpc/GrpcVorgangService.java
rename to api-lib-core/src/main/java/de/ozgcloud/apilib/vorgang/grpc/GrpcOzgCloudVorgangService.java
index a0a5982..bbcbcf7 100644
--- a/api-lib-core/src/main/java/de/ozgcloud/apilib/vorgang/grpc/GrpcVorgangService.java
+++ b/api-lib-core/src/main/java/de/ozgcloud/apilib/vorgang/grpc/GrpcOzgCloudVorgangService.java
@@ -22,7 +22,7 @@ import net.devh.boot.grpc.client.inject.GrpcClient;
 @Service
 @ConditionalOnProperty("ozgcloud.vorgang-manager.address")
 @RequiredArgsConstructor
-public class GrpcVorgangService implements OzgCloudVorgangService {
+public class GrpcOzgCloudVorgangService implements OzgCloudVorgangService {
 
 	@GrpcClient("vorgang-manager")
 	private final VorgangServiceBlockingStub vorgangServiceStub;
diff --git a/api-lib-core/src/test/java/de/ozgcloud/apilib/vorgang/grpc/GrpcVorgangServiceTest.java b/api-lib-core/src/test/java/de/ozgcloud/apilib/vorgang/grpc/GrpcOzgCloudVorgangServiceTest.java
similarity index 97%
rename from api-lib-core/src/test/java/de/ozgcloud/apilib/vorgang/grpc/GrpcVorgangServiceTest.java
rename to api-lib-core/src/test/java/de/ozgcloud/apilib/vorgang/grpc/GrpcOzgCloudVorgangServiceTest.java
index 0f9cc3c..ad87c0e 100644
--- a/api-lib-core/src/test/java/de/ozgcloud/apilib/vorgang/grpc/GrpcVorgangServiceTest.java
+++ b/api-lib-core/src/test/java/de/ozgcloud/apilib/vorgang/grpc/GrpcOzgCloudVorgangServiceTest.java
@@ -21,10 +21,10 @@ import de.ozgcloud.apilib.vorgang.OzgCloudVorgangStubTestFactory;
 import de.ozgcloud.apilib.vorgang.OzgCloudVorgangTestFactory;
 import de.ozgcloud.apilib.vorgang.Page;
 
-class GrpcVorgangServiceTest {
+class GrpcOzgCloudVorgangServiceTest {
 
 	@InjectMocks
-	private GrpcVorgangService service;
+	private GrpcOzgCloudVorgangService service;
 
 	@Mock
 	private VorgangServiceBlockingStub stub;
diff --git a/api-lib-demo/src/main/java/de/ozgcloud/apilib/demo/DemoRunner.java b/api-lib-demo/src/main/java/de/ozgcloud/apilib/demo/DemoRunner.java
index ba3b61d..b3709fc 100644
--- a/api-lib-demo/src/main/java/de/ozgcloud/apilib/demo/DemoRunner.java
+++ b/api-lib-demo/src/main/java/de/ozgcloud/apilib/demo/DemoRunner.java
@@ -20,7 +20,7 @@ class DemoRunner implements ApplicationListener<ContextRefreshedEvent> {
 
 	@Override
 	public void onApplicationEvent(ContextRefreshedEvent event) {
-		System.out.println(vorgangService.getById(OzgCloudVorgangId.from("647885a50b105b1e4995378e")));
+		System.out.println(vorgangService.getById(OzgCloudVorgangId.from("64bfd6597a08cf6e8a5185c6")));
 
 		System.out.println(fileService.getFile(OzgCloudFileId.from("630363d5b5816c0d8efd6f19")));
 
diff --git a/ozg-cloud-spring-boot-starter/src/main/java/de/ozgcloud/client/autoconfigure/OzgCloudClientAutoConfiguration.java b/ozg-cloud-spring-boot-starter/src/main/java/de/ozgcloud/client/autoconfigure/OzgCloudClientAutoConfiguration.java
index b27610b..41f4957 100644
--- a/ozg-cloud-spring-boot-starter/src/main/java/de/ozgcloud/client/autoconfigure/OzgCloudClientAutoConfiguration.java
+++ b/ozg-cloud-spring-boot-starter/src/main/java/de/ozgcloud/client/autoconfigure/OzgCloudClientAutoConfiguration.java
@@ -4,15 +4,20 @@ import java.util.Map;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Import;
 
+import de.ozgcloud.apilib.common.callcontext.DefaultOzgCloudCallContextProvider;
+import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor;
+import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider;
 import de.ozgcloud.apilib.file.dummy.DummyOzgCloudFileService;
 import de.ozgcloud.apilib.file.grpc.GrpcOzgCloudFileService;
 import de.ozgcloud.apilib.vorgang.dummy.DummyVorgangService;
-import de.ozgcloud.apilib.vorgang.grpc.GrpcVorgangService;
+import de.ozgcloud.apilib.vorgang.grpc.GrpcOzgCloudVorgangService;
 import net.devh.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration;
 import net.devh.boot.grpc.client.config.GrpcChannelProperties;
 import net.devh.boot.grpc.client.config.GrpcChannelsProperties;
@@ -20,8 +25,9 @@ import net.devh.boot.grpc.client.config.GrpcChannelsProperties;
 @AutoConfiguration(before = GrpcClientAutoConfiguration.class)
 @ComponentScan("de.ozgcloud.client.autoconfigure")
 @Import({
-		GrpcVorgangService.class, DummyVorgangService.class,
-		GrpcOzgCloudFileService.class, DummyOzgCloudFileService.class
+		GrpcOzgCloudVorgangService.class, DummyVorgangService.class,
+		GrpcOzgCloudFileService.class, DummyOzgCloudFileService.class,
+		OzgCloudCallContextAttachingInterceptor.class
 })
 public class OzgCloudClientAutoConfiguration {
 
@@ -60,4 +66,10 @@ public class OzgCloudClientAutoConfiguration {
 
 		clientMap.put(CLIENT_NAME_FILE_MANAGER, channelProps);
 	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	OzgCloudCallContextProvider callContextProvider(ApplicationContext ctxt) {
+		return new DefaultOzgCloudCallContextProvider(ctxt);
+	}
 }
-- 
GitLab