Skip to content

Commit 94017a6

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

File tree

6 files changed

+320
-93
lines changed

6 files changed

+320
-93
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

+97-70
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,35 +27,34 @@
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;
54-
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;
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;
5857
import org.springframework.web.reactive.function.client.WebClient;
59-
import reactor.core.publisher.Mono;
6058

6159
/**
6260
* This is a basic implementation of {@link BasicChatbotClient}. It does not perform session
@@ -67,41 +65,45 @@
6765
*/
6866
public class BasicChatbotClientImpl implements BasicChatbotClient {
6967

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

7778
protected BasicChatbotClientImpl(String basePath,
7879
AuthMechanism authMechanism,
7980
WebClient.Builder webClientBuilder) {
8081

8182
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);
83+
this.basePath = basePath;
84+
this.webClientBuilder = webClientBuilder;
85+
this.clientWrapper = ClientFactory.createClient(basePath, webClientBuilder);
86+
this.cache = new InMemoryCache(DEFAULT_TTL_SECONDS);
9087
}
9188

9289
@VisibleForTesting
9390
void setBotApi(BotApi botApi) {
94-
this.botApi = botApi;
91+
this.clientWrapper.setBotApi(botApi);
9592
}
9693

9794
@VisibleForTesting
9895
void setHealthApi(HealthApi healthApi) {
99-
this.healthApi = healthApi;
96+
this.clientWrapper.setHealthApi(healthApi);
10097
}
10198

10299
@VisibleForTesting
103100
void setVersionsApi(VersionsApi versionsApi) {
104-
this.versionsApi = versionsApi;
101+
this.clientWrapper.setVersionsApi(versionsApi);
102+
}
103+
104+
@VisibleForTesting
105+
void setCache(InMemoryCache cache) {
106+
this.cache = cache;
105107
}
106108

107109
@Override
@@ -112,15 +114,28 @@ public BotResponse startChatSession(RequestConfig config,
112114
if (!isApiVersionSupported()) {
113115
throw new UnsupportedSDKException(getCurrentApiVersion(), getLatestApiVersion());
114116
}
117+
118+
String basePath = getRuntimeUrl(config.getForceConfigEndpoint());
119+
Optional<Object> clientOptional = this.cache.getObject(basePath);
120+
ClientWrapper clientWrapper = ClientFactory.createClient(basePath, webClientBuilder);
121+
if (clientOptional.isPresent()) {
122+
clientWrapper = (ClientWrapper) clientOptional.get();
123+
}
124+
this.clientWrapper = clientWrapper;
125+
115126
InitMessageEnvelope initMessageEnvelope = createInitMessageEnvelope(config, sessionId,
116127
botSendMessageRequest);
117128

118129
notifyRequestEnvelopeInterceptor(botSendMessageRequest, initMessageEnvelope);
119130
CompletableFuture<BotResponse> futureResponse = invokeEstablishChatSession(config,
120131
initMessageEnvelope,
121-
botSendMessageRequest);
132+
botSendMessageRequest,
133+
clientWrapper);
122134
try {
123-
return futureResponse.get();
135+
BotResponse botResponse = futureResponse.get();
136+
this.cache.set(botResponse.getResponseEnvelope().getSessionId(), basePath);
137+
this.cache.setObject(basePath, clientWrapper);
138+
return botResponse;
124139
} catch (InterruptedException | ExecutionException e) {
125140
throw new RuntimeException(e);
126141
}
@@ -141,11 +156,13 @@ public BotResponse sendMessage(RequestConfig config,
141156

142157
ChatMessageEnvelope chatMessageEnvelope = createChatMessageEnvelope(botSendMessageRequest);
143158

159+
ClientWrapper clientWrapper = getCachedClientWrapper(sessionId);
144160
notifyRequestEnvelopeInterceptor(botSendMessageRequest, chatMessageEnvelope);
145161
CompletableFuture<BotResponse> futureResponse = invokeContinueChatSession(config.getOrgId(),
146162
sessionId.getValue(),
147163
chatMessageEnvelope,
148-
botSendMessageRequest);
164+
botSendMessageRequest,
165+
clientWrapper);
149166

150167
try {
151168
return futureResponse.get();
@@ -166,11 +183,14 @@ public BotResponse endChatSession(RequestConfig config,
166183
BotEndSessionRequest botEndSessionRequest) {
167184

168185
EndSessionReason endSessionReason = botEndSessionRequest.getEndSessionReason();
186+
187+
ClientWrapper clientWrapper = getCachedClientWrapper(sessionId);
169188
notifyRequestEnvelopeInterceptor(botEndSessionRequest, "EndSessionReason: " + endSessionReason);
170189
CompletableFuture<BotResponse> futureResponse = invokeEndChatSession(config.getOrgId(),
171190
sessionId.getValue(),
172191
endSessionReason,
173-
botEndSessionRequest);
192+
botEndSessionRequest,
193+
clientWrapper);
174194
try {
175195
return futureResponse.get();
176196
} catch (InterruptedException | ExecutionException e) {
@@ -183,11 +203,23 @@ protected void notifyRequestEnvelopeInterceptor(BotRequest botRequest, Object re
183203
.accept(requestEnvelope);
184204
}
185205

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

189-
apiClient.setBearerToken(authMechanism.getToken());
190-
CompletableFuture<BotResponse> futureResponse = botApi
221+
clientWrapper.getApiClient().setBearerToken(authMechanism.getToken());
222+
CompletableFuture<BotResponse> futureResponse = clientWrapper.getBotApi()
191223
.endSessionWithHttpInfo(sessionId,
192224
orgId,
193225
endSessionReason,
@@ -202,10 +234,11 @@ protected CompletableFuture<BotResponse> invokeEndChatSession(String orgId, Stri
202234

203235
protected CompletableFuture<BotResponse> invokeEstablishChatSession(RequestConfig config,
204236
InitMessageEnvelope initMessageEnvelope,
205-
BotSendMessageRequest botRequest) {
237+
BotSendMessageRequest botRequest,
238+
ClientWrapper clientWrapper) {
206239

207-
apiClient.setBearerToken(authMechanism.getToken());
208-
CompletableFuture<BotResponse> futureResponse = botApi
240+
clientWrapper.getApiClient().setBearerToken(authMechanism.getToken());
241+
CompletableFuture<BotResponse> futureResponse = clientWrapper.getBotApi()
209242
.startSessionWithHttpInfo(config.getBotId(), config.getOrgId(),
210243
initMessageEnvelope, botRequest.getRequestId().orElse(null))
211244
.toFuture()
@@ -216,10 +249,11 @@ protected CompletableFuture<BotResponse> invokeEstablishChatSession(RequestConfi
216249

217250
protected CompletableFuture<BotResponse> invokeContinueChatSession(String orgId, String sessionId,
218251
ChatMessageEnvelope messageEnvelope,
219-
BotSendMessageRequest botRequest) {
252+
BotSendMessageRequest botRequest,
253+
ClientWrapper clientWrapper) {
220254

221-
apiClient.setBearerToken(authMechanism.getToken());
222-
CompletableFuture<BotResponse> futureResponse = botApi
255+
clientWrapper.getApiClient().setBearerToken(authMechanism.getToken());
256+
CompletableFuture<BotResponse> futureResponse = clientWrapper.getBotApi()
223257
.continueSessionWithHttpInfo(sessionId,
224258
orgId,
225259
messageEnvelope,
@@ -233,7 +267,7 @@ protected CompletableFuture<BotResponse> invokeContinueChatSession(String orgId,
233267
}
234268

235269
public Status getHealthStatus() {
236-
CompletableFuture<Status> statusFuture = healthApi.checkHealthStatus().toFuture();
270+
CompletableFuture<Status> statusFuture = this.clientWrapper.getHealthApi().checkHealthStatus().toFuture();
237271

238272
try {
239273
return statusFuture.get();
@@ -243,7 +277,7 @@ public Status getHealthStatus() {
243277
}
244278

245279
public SupportedVersions getSupportedVersions() {
246-
CompletableFuture<SupportedVersions> versionsFuture = versionsApi.getAPIVersions().toFuture();
280+
CompletableFuture<SupportedVersions> versionsFuture = this.clientWrapper.getVersionsApi().getAPIVersions().toFuture();
247281

248282
try {
249283
SupportedVersions versions = versionsFuture.get();
@@ -256,30 +290,23 @@ public SupportedVersions getSupportedVersions() {
256290
}
257291
}
258292

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())));
293+
private String getRuntimeUrl(String forceEndpoint) {
294+
try {
295+
URI uri = URI.create(forceEndpoint);
296+
HttpHost forceHost = URIUtils.extractHost(uri);
297+
String infoPath = uri.getRawPath().replace("/$", "") + API_INFO_URI;
298+
HttpGet httpGet = new HttpGet(forceHost.toString() + infoPath);
299+
httpGet.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + this.authMechanism.getToken());
300+
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
301+
return httpClient.execute(httpGet, httpResponse -> {
302+
String response = EntityUtils.toString(httpResponse.getEntity());
303+
JsonNode node = MAPPER.readValue(response, JsonNode.class);
304+
return node.get("runtimeBaseUrl").asText();
305+
});
306+
}
307+
} catch (Exception ex) {
308+
throw new RuntimeException(ex);
309+
}
283310
}
284311

285312
private String getCurrentApiVersion() {

0 commit comments

Comments
 (0)