|
1 | 1 | package com.sap.ai.sdk.core; |
2 | 2 |
|
3 | | -import static com.google.common.collect.Iterables.tryFind; |
4 | 3 | import static com.sap.cloud.sdk.cloudplatform.connectivity.OnBehalfOf.TECHNICAL_USER_PROVIDER; |
5 | | -import static com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationOptions.forService; |
6 | 4 |
|
7 | | -import com.fasterxml.jackson.core.JsonProcessingException; |
8 | | -import com.fasterxml.jackson.core.type.TypeReference; |
9 | | -import com.fasterxml.jackson.databind.ObjectMapper; |
10 | 5 | import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; |
11 | | -import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingBuilder; |
| 6 | +import com.sap.cloud.environment.servicebinding.api.ServiceBinding; |
12 | 7 | import com.sap.cloud.environment.servicebinding.api.ServiceBindingAccessor; |
13 | 8 | import com.sap.cloud.environment.servicebinding.api.ServiceBindingMerger; |
14 | 9 | import com.sap.cloud.environment.servicebinding.api.ServiceIdentifier; |
| 10 | +import com.sap.cloud.environment.servicebinding.api.exception.ServiceBindingAccessException; |
15 | 11 | import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; |
16 | 12 | import com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationLoader; |
17 | | -import java.util.HashMap; |
| 13 | +import com.sap.cloud.sdk.cloudplatform.connectivity.ServiceBindingDestinationOptions; |
18 | 14 | import java.util.List; |
19 | | -import java.util.Optional; |
20 | | -import java.util.function.Predicate; |
21 | 15 | import javax.annotation.Nonnull; |
22 | | -import javax.annotation.Nullable; |
23 | | -import lombok.AccessLevel; |
24 | | -import lombok.Getter; |
25 | | -import lombok.NoArgsConstructor; |
| 16 | +import lombok.AllArgsConstructor; |
26 | 17 | import lombok.extern.slf4j.Slf4j; |
27 | 18 | import lombok.val; |
28 | 19 |
|
29 | 20 | /** Utility class to resolve the destination pointing to the AI Core service. */ |
30 | 21 | @Slf4j |
31 | | -@NoArgsConstructor(access = AccessLevel.PRIVATE) // utility class should not be instantiated |
| 22 | +@AllArgsConstructor |
32 | 23 | class DestinationResolver { |
33 | | - static final String AI_CLIENT_TYPE_KEY = "URL.headers.AI-Client-Type"; |
34 | | - static final String AI_CLIENT_TYPE_VALUE = "AI SDK Java"; |
| 24 | + @Nonnull private final ServiceBindingAccessor accessor; |
35 | 25 |
|
36 | | - @Getter(AccessLevel.PROTECTED) |
37 | | - @Nonnull |
38 | | - private static ServiceBindingAccessor accessor = DefaultServiceBindingAccessor.getInstance(); |
| 26 | + DestinationResolver() { |
| 27 | + this( |
| 28 | + new ServiceBindingMerger( |
| 29 | + List.of(DefaultServiceBindingAccessor.getInstance(), new AiCoreServiceKeyAccessor()), |
| 30 | + ServiceBindingMerger.KEEP_EVERYTHING)); |
| 31 | + } |
39 | 32 |
|
40 | | - /** |
41 | | - * <b>For testing only</b> |
42 | | - * |
43 | | - * <p>Get a destination pointing to the AI Core service. |
44 | | - * |
45 | | - * @param serviceKey The service key in JSON format. |
46 | | - * @return a destination pointing to the AI Core service. |
47 | | - */ |
48 | 33 | @SuppressWarnings("UnstableApiUsage") |
49 | | - static HttpDestination getDestination(@Nullable final String serviceKey) { |
50 | | - final Predicate<Object> aiCore = Optional.of(ServiceIdentifier.AI_CORE)::equals; |
51 | | - val serviceBindings = accessor.getServiceBindings(); |
52 | | - val aiCoreBinding = tryFind(serviceBindings, b -> aiCore.test(b.getServiceIdentifier())); |
53 | | - |
54 | | - val serviceKeyPresent = serviceKey != null; |
55 | | - if (!aiCoreBinding.isPresent() && serviceKeyPresent) { |
56 | | - addServiceBinding(serviceKey); |
57 | | - } |
| 34 | + HttpDestination getDestination() { |
| 35 | + val binding = |
| 36 | + accessor.getServiceBindings().stream() |
| 37 | + .filter(DestinationResolver::isAiCoreService) |
| 38 | + .findFirst() |
| 39 | + .orElseThrow( |
| 40 | + () -> |
| 41 | + new ServiceBindingAccessException( |
| 42 | + "Could not find any matching service bindings for service identifier " |
| 43 | + + ServiceIdentifier.AI_CORE)); |
58 | 44 |
|
59 | 45 | // get a destination pointing to the AI Core service |
60 | 46 | val opts = |
61 | | - (aiCoreBinding.isPresent() |
62 | | - ? forService(aiCoreBinding.get()) |
63 | | - : forService(ServiceIdentifier.AI_CORE)) |
| 47 | + ServiceBindingDestinationOptions.forService(binding) |
64 | 48 | .onBehalfOf(TECHNICAL_USER_PROVIDER) |
65 | 49 | .build(); |
66 | 50 |
|
67 | 51 | return ServiceBindingDestinationLoader.defaultLoaderChain().getDestination(opts); |
68 | 52 | } |
69 | 53 |
|
70 | | - /** |
71 | | - * Set the AI Core service key as the service binding. This is used for local testing. |
72 | | - * |
73 | | - * @param serviceKey The service key in JSON format. |
74 | | - * @throws AiCoreCredentialsInvalidException if the JSON service key cannot be parsed. |
75 | | - */ |
76 | | - private static void addServiceBinding(@Nonnull final String serviceKey) { |
77 | | - log.info( |
78 | | - """ |
79 | | - Found a service key in environment variable "AICORE_SERVICE_KEY". |
80 | | - Using a service key is recommended for local testing only. |
81 | | - Bind the AI Core service to the application for productive usage."""); |
82 | | - |
83 | | - var credentials = new HashMap<String, Object>(); |
84 | | - try { |
85 | | - credentials = new ObjectMapper().readValue(serviceKey, new TypeReference<>() {}); |
86 | | - } catch (JsonProcessingException e) { |
87 | | - throw new AiCoreCredentialsInvalidException( |
88 | | - "Error in parsing service key from the \"AICORE_SERVICE_KEY\" environment variable.", e); |
89 | | - } |
90 | | - |
91 | | - val binding = |
92 | | - new DefaultServiceBindingBuilder() |
93 | | - .withServiceIdentifier(ServiceIdentifier.AI_CORE) |
94 | | - .withCredentials(credentials) |
95 | | - .build(); |
96 | | - val newAccessor = |
97 | | - new ServiceBindingMerger( |
98 | | - List.of(accessor, () -> List.of(binding)), ServiceBindingMerger.KEEP_EVERYTHING); |
99 | | - DefaultServiceBindingAccessor.setInstance(newAccessor); |
100 | | - } |
101 | | - |
102 | | - /** Exception thrown when the JSON AI Core service key is invalid. */ |
103 | | - static class AiCoreCredentialsInvalidException extends RuntimeException { |
104 | | - public AiCoreCredentialsInvalidException( |
105 | | - @Nonnull final String message, @Nonnull final Throwable cause) { |
106 | | - super(message, cause); |
107 | | - } |
108 | | - } |
109 | | - |
110 | | - /** |
111 | | - * For testing set the accessor to be used for service binding resolution. |
112 | | - * |
113 | | - * @param accessor The accessor to be used for service binding resolution. |
114 | | - */ |
115 | | - static void setAccessor(@Nullable final ServiceBindingAccessor accessor) { |
116 | | - DestinationResolver.accessor = |
117 | | - accessor == null ? DefaultServiceBindingAccessor.getInstance() : accessor; |
| 54 | + private static boolean isAiCoreService(@Nonnull final ServiceBinding binding) { |
| 55 | + return ServiceIdentifier.AI_CORE.equals(binding.getServiceIdentifier().orElse(null)); |
118 | 56 | } |
119 | 57 | } |
0 commit comments