Skip to content

Commit 9f027e2

Browse files
committed
Update SDK to automatically pull runtime URL when a session starts
1 parent 210a52a commit 9f027e2

File tree

6 files changed

+326
-91
lines changed

6 files changed

+326
-91
lines changed

src/main/java/com/salesforce/einsteinbot/sdk/cache/InMemoryCache.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@
1717
*/
1818
public class InMemoryCache implements Cache {
1919

20-
private final com.google.common.cache.Cache<String, String> cache;
20+
private final com.google.common.cache.Cache<String, Object> cache;
2121

2222
public InMemoryCache(long ttlSeconds) {
2323
cache = CacheBuilder.newBuilder().expireAfterAccess(ttlSeconds, TimeUnit.SECONDS).build();
2424
}
2525

2626
@Override
2727
public Optional<String> get(String key) {
28-
String val = cache.getIfPresent(key);
28+
String val = (String) cache.getIfPresent(key);
29+
return Optional.ofNullable(val);
30+
}
31+
32+
public Optional<Object> getObject(String key) {
33+
Object val = cache.getIfPresent(key);
2934
return Optional.ofNullable(val);
3035
}
3136

@@ -34,12 +39,20 @@ public void set(String key, String val) {
3439
cache.put(key, val);
3540
}
3641

42+
public void setObject(String key, Object val) {
43+
cache.put(key, val);
44+
}
45+
3746
/**
3847
* This method does not respect the ttlSeconds parameter.
3948
*/
4049
@Override
4150
public void set(String key, String val, long ttlSeconds) {
42-
cache.put(key, val);
51+
set(key, val);
52+
}
53+
54+
public void setObject(String key, Object val, long ttlSeconds) {
55+
setObject(key, val);
4356
}
4457

4558
@Override

src/main/java/com/salesforce/einsteinbot/sdk/cache/RedisCache.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class RedisCache implements Cache {
2525
private static final Long DEFAULT_TTL_SECONDS = 259140L; // 2 days, 23 hours, 59 minutes
2626

2727
private JedisPool jedisPool;
28-
private long ttlSeconds;
28+
private final long ttlSeconds;
2929

3030
/**
3131
* This constructor will use the default ttl of 259,140 seconds and will assume standard Redis

src/main/java/com/salesforce/einsteinbot/sdk/client/BasicChatbotClientImpl.java

+103-68
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@
1010
import static com.salesforce.einsteinbot.sdk.client.model.BotResponseBuilder.fromChatMessageResponseEnvelopeResponseEntity;
1111
import static com.salesforce.einsteinbot.sdk.client.util.RequestFactory.buildChatMessageEnvelope;
1212
import static com.salesforce.einsteinbot.sdk.client.util.RequestFactory.buildInitMessageEnvelope;
13-
import static com.salesforce.einsteinbot.sdk.util.WebClientUtil.createErrorResponseProcessor;
14-
import static com.salesforce.einsteinbot.sdk.util.WebClientUtil.createFilter;
15-
import static com.salesforce.einsteinbot.sdk.util.WebClientUtil.createLoggingRequestProcessor;
1613

14+
import com.fasterxml.jackson.databind.JsonNode;
1715
import com.fasterxml.jackson.databind.ObjectMapper;
1816
import com.google.common.annotations.VisibleForTesting;
1917
import com.salesforce.einsteinbot.sdk.api.BotApi;
2018
import com.salesforce.einsteinbot.sdk.api.HealthApi;
2119
import com.salesforce.einsteinbot.sdk.api.VersionsApi;
2220
import com.salesforce.einsteinbot.sdk.auth.AuthMechanism;
21+
import com.salesforce.einsteinbot.sdk.cache.InMemoryCache;
2322
import com.salesforce.einsteinbot.sdk.client.model.BotEndSessionRequest;
2423
import com.salesforce.einsteinbot.sdk.client.model.BotRequest;
2524
import com.salesforce.einsteinbot.sdk.client.model.BotResponse;
@@ -28,34 +27,36 @@
2827
import com.salesforce.einsteinbot.sdk.client.model.ExternalSessionId;
2928
import com.salesforce.einsteinbot.sdk.client.model.RequestConfig;
3029
import com.salesforce.einsteinbot.sdk.client.model.RuntimeSessionId;
31-
import com.salesforce.einsteinbot.sdk.exception.ChatbotResponseException;
30+
import com.salesforce.einsteinbot.sdk.client.util.ClientFactory;
31+
import com.salesforce.einsteinbot.sdk.client.util.ClientFactory.ClientWrapper;
3232
import com.salesforce.einsteinbot.sdk.exception.UnsupportedSDKException;
33-
import com.salesforce.einsteinbot.sdk.handler.ApiClient;
3433
import com.salesforce.einsteinbot.sdk.model.ChatMessageEnvelope;
3534
import com.salesforce.einsteinbot.sdk.model.EndSessionReason;
3635
import com.salesforce.einsteinbot.sdk.model.InitMessageEnvelope;
3736
import com.salesforce.einsteinbot.sdk.model.Status;
3837
import com.salesforce.einsteinbot.sdk.model.SupportedVersions;
3938
import com.salesforce.einsteinbot.sdk.model.SupportedVersionsVersions;
4039
import com.salesforce.einsteinbot.sdk.model.SupportedVersionsVersions.StatusEnum;
41-
import com.salesforce.einsteinbot.sdk.util.LoggingJsonEncoder;
42-
import com.salesforce.einsteinbot.sdk.util.ReleaseInfo;
43-
import com.salesforce.einsteinbot.sdk.util.UtilFunctions;
4440
import java.io.IOException;
4541
import java.io.InputStream;
42+
import java.net.URI;
43+
import java.time.Duration;
4644
import java.util.Objects;
4745
import java.util.Optional;
4846
import java.util.Properties;
4947
import java.util.concurrent.CompletableFuture;
5048
import java.util.concurrent.ExecutionException;
51-
import java.util.function.Consumer;
5249

53-
import com.salesforce.einsteinbot.sdk.util.WebClientUtil;
50+
import org.apache.http.HttpHeaders;
51+
import org.apache.http.HttpHost;
52+
import org.apache.http.client.methods.HttpGet;
53+
import org.apache.http.client.utils.URIUtils;
54+
import org.apache.http.impl.client.CloseableHttpClient;
55+
import org.apache.http.impl.client.HttpClients;
56+
import org.apache.http.util.EntityUtils;
5457
import org.springframework.http.MediaType;
55-
import org.springframework.http.codec.ClientCodecConfigurer;
56-
import org.springframework.http.codec.json.Jackson2JsonDecoder;
57-
import org.springframework.web.reactive.function.client.ClientResponse;
5858
import org.springframework.web.reactive.function.client.WebClient;
59+
import reactor.core.publisher.Flux;
5960
import reactor.core.publisher.Mono;
6061

6162
/**
@@ -67,41 +68,45 @@
6768
*/
6869
public class BasicChatbotClientImpl implements BasicChatbotClient {
6970

70-
protected BotApi botApi;
71-
protected HealthApi healthApi;
72-
protected VersionsApi versionsApi;
73-
protected ApiClient apiClient;
71+
private static final Long DEFAULT_TTL_SECONDS = Duration.ofDays(3).getSeconds();
72+
private static final ObjectMapper MAPPER = new ObjectMapper();
73+
static final String API_INFO_URI = "/services/data/v58.0/connect/bots/api-info";
74+
75+
protected InMemoryCache cache;
76+
protected String basePath;
77+
protected WebClient.Builder webClientBuilder;
7478
protected AuthMechanism authMechanism;
75-
protected ReleaseInfo releaseInfo = ReleaseInfo.getInstance();
79+
protected ClientWrapper clientWrapper;
7680

7781
protected BasicChatbotClientImpl(String basePath,
7882
AuthMechanism authMechanism,
7983
WebClient.Builder webClientBuilder) {
8084

8185
this.authMechanism = authMechanism;
82-
this.apiClient = new ApiClient(createWebClient(webClientBuilder), UtilFunctions.getMapper(),
83-
UtilFunctions
84-
.createDefaultDateFormat());
85-
apiClient.setBasePath(basePath);
86-
apiClient.setUserAgent(releaseInfo.getAsUserAgent());
87-
botApi = new BotApi(apiClient);
88-
healthApi = new HealthApi(apiClient);
89-
versionsApi = new VersionsApi(apiClient);
86+
this.basePath = basePath;
87+
this.webClientBuilder = webClientBuilder;
88+
this.clientWrapper = ClientFactory.createClient(basePath, webClientBuilder);
89+
this.cache = new InMemoryCache(DEFAULT_TTL_SECONDS);
9090
}
9191

9292
@VisibleForTesting
9393
void setBotApi(BotApi botApi) {
94-
this.botApi = botApi;
94+
this.clientWrapper.setBotApi(botApi);
9595
}
9696

9797
@VisibleForTesting
9898
void setHealthApi(HealthApi healthApi) {
99-
this.healthApi = healthApi;
99+
this.clientWrapper.setHealthApi(healthApi);
100100
}
101101

102102
@VisibleForTesting
103103
void setVersionsApi(VersionsApi versionsApi) {
104-
this.versionsApi = versionsApi;
104+
this.clientWrapper.setVersionsApi(versionsApi);
105+
}
106+
107+
@VisibleForTesting
108+
void setCache(InMemoryCache cache) {
109+
this.cache = cache;
105110
}
106111

107112
@Override
@@ -112,15 +117,28 @@ public BotResponse startChatSession(RequestConfig config,
112117
if (!isApiVersionSupported()) {
113118
throw new UnsupportedSDKException(getCurrentApiVersion(), getLatestApiVersion());
114119
}
120+
121+
String basePath = getRuntimeUrl(config.getForceConfigEndpoint());
122+
Optional<Object> clientOptional = this.cache.getObject(basePath);
123+
ClientWrapper clientWrapper = ClientFactory.createClient(basePath, webClientBuilder);
124+
if (clientOptional.isPresent()) {
125+
clientWrapper = (ClientWrapper) clientOptional.get();
126+
}
127+
this.clientWrapper = clientWrapper;
128+
115129
InitMessageEnvelope initMessageEnvelope = createInitMessageEnvelope(config, sessionId,
116130
botSendMessageRequest);
117131

118132
notifyRequestEnvelopeInterceptor(botSendMessageRequest, initMessageEnvelope);
119133
CompletableFuture<BotResponse> futureResponse = invokeEstablishChatSession(config,
120134
initMessageEnvelope,
121-
botSendMessageRequest);
135+
botSendMessageRequest,
136+
clientWrapper);
122137
try {
123-
return futureResponse.get();
138+
BotResponse botResponse = futureResponse.get();
139+
this.cache.set(botResponse.getResponseEnvelope().getSessionId(), basePath);
140+
this.cache.setObject(basePath, clientWrapper);
141+
return botResponse;
124142
} catch (InterruptedException | ExecutionException e) {
125143
throw new RuntimeException(e);
126144
}
@@ -141,11 +159,13 @@ public BotResponse sendMessage(RequestConfig config,
141159

142160
ChatMessageEnvelope chatMessageEnvelope = createChatMessageEnvelope(botSendMessageRequest);
143161

162+
ClientWrapper clientWrapper = getCachedClientWrapper(sessionId);
144163
notifyRequestEnvelopeInterceptor(botSendMessageRequest, chatMessageEnvelope);
145164
CompletableFuture<BotResponse> futureResponse = invokeContinueChatSession(config.getOrgId(),
146165
sessionId.getValue(),
147166
chatMessageEnvelope,
148-
botSendMessageRequest);
167+
botSendMessageRequest,
168+
clientWrapper);
149169

150170
try {
151171
return futureResponse.get();
@@ -166,11 +186,14 @@ public BotResponse endChatSession(RequestConfig config,
166186
BotEndSessionRequest botEndSessionRequest) {
167187

168188
EndSessionReason endSessionReason = botEndSessionRequest.getEndSessionReason();
189+
190+
ClientWrapper clientWrapper = getCachedClientWrapper(sessionId);
169191
notifyRequestEnvelopeInterceptor(botEndSessionRequest, "EndSessionReason: " + endSessionReason);
170192
CompletableFuture<BotResponse> futureResponse = invokeEndChatSession(config.getOrgId(),
171193
sessionId.getValue(),
172194
endSessionReason,
173-
botEndSessionRequest);
195+
botEndSessionRequest,
196+
clientWrapper);
174197
try {
175198
return futureResponse.get();
176199
} catch (InterruptedException | ExecutionException e) {
@@ -183,11 +206,23 @@ protected void notifyRequestEnvelopeInterceptor(BotRequest botRequest, Object re
183206
.accept(requestEnvelope);
184207
}
185208

209+
private ClientWrapper getCachedClientWrapper(RuntimeSessionId sessionId) {
210+
Optional<String> basePath = this.cache.get(sessionId.getValue());
211+
if (!basePath.isPresent()) {
212+
throw new RuntimeException("No base path found in cache for session ID: " + sessionId.getValue());
213+
}
214+
Optional<Object> clientOptional = this.cache.getObject(basePath.get());
215+
if (!clientOptional.isPresent()) {
216+
throw new RuntimeException("No client implementation found in cache for base path: " + basePath.get());
217+
}
218+
return (ClientWrapper) clientOptional.get();
219+
}
220+
186221
protected CompletableFuture<BotResponse> invokeEndChatSession(String orgId, String sessionId,
187-
EndSessionReason endSessionReason, BotEndSessionRequest botRequest) {
222+
EndSessionReason endSessionReason, BotEndSessionRequest botRequest, ClientWrapper clientWrapper) {
188223

189-
apiClient.setBearerToken(authMechanism.getToken());
190-
CompletableFuture<BotResponse> futureResponse = botApi
224+
clientWrapper.getApiClient().setBearerToken(authMechanism.getToken());
225+
CompletableFuture<BotResponse> futureResponse = clientWrapper.getBotApi()
191226
.endSessionWithHttpInfo(sessionId,
192227
orgId,
193228
endSessionReason,
@@ -202,10 +237,11 @@ protected CompletableFuture<BotResponse> invokeEndChatSession(String orgId, Stri
202237

203238
protected CompletableFuture<BotResponse> invokeEstablishChatSession(RequestConfig config,
204239
InitMessageEnvelope initMessageEnvelope,
205-
BotSendMessageRequest botRequest) {
240+
BotSendMessageRequest botRequest,
241+
ClientWrapper clientWrapper) {
206242

207-
apiClient.setBearerToken(authMechanism.getToken());
208-
CompletableFuture<BotResponse> futureResponse = botApi
243+
clientWrapper.getApiClient().setBearerToken(authMechanism.getToken());
244+
CompletableFuture<BotResponse> futureResponse = clientWrapper.getBotApi()
209245
.startSessionWithHttpInfo(config.getBotId(), config.getOrgId(),
210246
initMessageEnvelope, botRequest.getRequestId().orElse(null))
211247
.toFuture()
@@ -216,10 +252,11 @@ protected CompletableFuture<BotResponse> invokeEstablishChatSession(RequestConfi
216252

217253
protected CompletableFuture<BotResponse> invokeContinueChatSession(String orgId, String sessionId,
218254
ChatMessageEnvelope messageEnvelope,
219-
BotSendMessageRequest botRequest) {
255+
BotSendMessageRequest botRequest,
256+
ClientWrapper clientWrapper) {
220257

221-
apiClient.setBearerToken(authMechanism.getToken());
222-
CompletableFuture<BotResponse> futureResponse = botApi
258+
clientWrapper.getApiClient().setBearerToken(authMechanism.getToken());
259+
CompletableFuture<BotResponse> futureResponse = clientWrapper.getBotApi()
223260
.continueSessionWithHttpInfo(sessionId,
224261
orgId,
225262
messageEnvelope,
@@ -233,7 +270,7 @@ protected CompletableFuture<BotResponse> invokeContinueChatSession(String orgId,
233270
}
234271

235272
public Status getHealthStatus() {
236-
CompletableFuture<Status> statusFuture = healthApi.checkHealthStatus().toFuture();
273+
CompletableFuture<Status> statusFuture = this.clientWrapper.getHealthApi().checkHealthStatus().toFuture();
237274

238275
try {
239276
return statusFuture.get();
@@ -243,7 +280,7 @@ public Status getHealthStatus() {
243280
}
244281

245282
public SupportedVersions getSupportedVersions() {
246-
CompletableFuture<SupportedVersions> versionsFuture = versionsApi.getAPIVersions().toFuture();
283+
CompletableFuture<SupportedVersions> versionsFuture = this.clientWrapper.getVersionsApi().getAPIVersions().toFuture();
247284

248285
try {
249286
SupportedVersions versions = versionsFuture.get();
@@ -256,30 +293,28 @@ public SupportedVersions getSupportedVersions() {
256293
}
257294
}
258295

259-
private WebClient createWebClient(WebClient.Builder webClientBuilder) {
260-
261-
return webClientBuilder
262-
.codecs(createCodecsConfiguration(UtilFunctions.getMapper()))
263-
.filter(createFilter(clientRequest -> createLoggingRequestProcessor(clientRequest),
264-
clientResponse -> createErrorResponseProcessor(clientResponse, this::mapErrorResponse)))
265-
.build();
266-
}
267-
268-
private Consumer<ClientCodecConfigurer> createCodecsConfiguration(ObjectMapper mapper) {
269-
return clientDefaultCodecsConfigurer -> {
270-
clientDefaultCodecsConfigurer.defaultCodecs()
271-
.jackson2JsonEncoder(new LoggingJsonEncoder(mapper, MediaType.APPLICATION_JSON, false));
272-
clientDefaultCodecsConfigurer.defaultCodecs()
273-
.jackson2JsonDecoder(new Jackson2JsonDecoder(mapper, MediaType.APPLICATION_JSON));
274-
};
275-
}
276-
277-
private Mono<ClientResponse> mapErrorResponse(ClientResponse clientResponse) {
278-
return clientResponse
279-
.body(WebClientUtil.errorBodyExtractor())
280-
.flatMap(errorDetails -> Mono
281-
.error(new ChatbotResponseException(clientResponse.statusCode(), errorDetails,
282-
clientResponse.headers())));
296+
private String getRuntimeUrl(String forceEndpoint) {
297+
try {
298+
URI uri = URI.create(forceEndpoint);
299+
HttpHost forceHost = URIUtils.extractHost(uri);
300+
String infoPath = uri.getRawPath().replace("/$", "") + API_INFO_URI;
301+
WebClient webClient = WebClient.builder()
302+
.baseUrl(forceHost.toString())
303+
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
304+
.build();
305+
JsonNode node = webClient.get()
306+
.uri(infoPath)
307+
.header(HttpHeaders.AUTHORIZATION, "Bearer " + this.authMechanism.getToken())
308+
.retrieve()
309+
.bodyToMono(JsonNode.class)
310+
.block();
311+
if (node == null) {
312+
throw new RuntimeException("Could not get runtime URL");
313+
}
314+
return node.get("runtimeBaseUrl").asText();
315+
} catch (Exception ex) {
316+
throw new RuntimeException(ex);
317+
}
283318
}
284319

285320
private String getCurrentApiVersion() {

0 commit comments

Comments
 (0)