10
10
import static com .salesforce .einsteinbot .sdk .client .model .BotResponseBuilder .fromChatMessageResponseEnvelopeResponseEntity ;
11
11
import static com .salesforce .einsteinbot .sdk .client .util .RequestFactory .buildChatMessageEnvelope ;
12
12
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 ;
16
13
14
+ import com .fasterxml .jackson .databind .JsonNode ;
17
15
import com .fasterxml .jackson .databind .ObjectMapper ;
18
16
import com .google .common .annotations .VisibleForTesting ;
19
17
import com .salesforce .einsteinbot .sdk .api .BotApi ;
20
18
import com .salesforce .einsteinbot .sdk .api .HealthApi ;
21
19
import com .salesforce .einsteinbot .sdk .api .VersionsApi ;
22
20
import com .salesforce .einsteinbot .sdk .auth .AuthMechanism ;
21
+ import com .salesforce .einsteinbot .sdk .cache .InMemoryCache ;
23
22
import com .salesforce .einsteinbot .sdk .client .model .BotEndSessionRequest ;
24
23
import com .salesforce .einsteinbot .sdk .client .model .BotRequest ;
25
24
import com .salesforce .einsteinbot .sdk .client .model .BotResponse ;
28
27
import com .salesforce .einsteinbot .sdk .client .model .ExternalSessionId ;
29
28
import com .salesforce .einsteinbot .sdk .client .model .RequestConfig ;
30
29
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 ;
32
32
import com .salesforce .einsteinbot .sdk .exception .UnsupportedSDKException ;
33
- import com .salesforce .einsteinbot .sdk .handler .ApiClient ;
34
33
import com .salesforce .einsteinbot .sdk .model .ChatMessageEnvelope ;
35
34
import com .salesforce .einsteinbot .sdk .model .EndSessionReason ;
36
35
import com .salesforce .einsteinbot .sdk .model .InitMessageEnvelope ;
37
36
import com .salesforce .einsteinbot .sdk .model .Status ;
38
37
import com .salesforce .einsteinbot .sdk .model .SupportedVersions ;
39
38
import com .salesforce .einsteinbot .sdk .model .SupportedVersionsVersions ;
40
39
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 ;
44
40
import java .io .IOException ;
45
41
import java .io .InputStream ;
42
+ import java .net .URI ;
43
+ import java .time .Duration ;
46
44
import java .util .Objects ;
47
45
import java .util .Optional ;
48
46
import java .util .Properties ;
49
47
import java .util .concurrent .CompletableFuture ;
50
48
import java .util .concurrent .ExecutionException ;
51
- import java .util .function .Consumer ;
52
49
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 ;
54
57
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 ;
58
58
import org .springframework .web .reactive .function .client .WebClient ;
59
+ import reactor .core .publisher .Flux ;
59
60
import reactor .core .publisher .Mono ;
60
61
61
62
/**
67
68
*/
68
69
public class BasicChatbotClientImpl implements BasicChatbotClient {
69
70
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 ;
74
78
protected AuthMechanism authMechanism ;
75
- protected ReleaseInfo releaseInfo = ReleaseInfo . getInstance () ;
79
+ protected ClientWrapper clientWrapper ;
76
80
77
81
protected BasicChatbotClientImpl (String basePath ,
78
82
AuthMechanism authMechanism ,
79
83
WebClient .Builder webClientBuilder ) {
80
84
81
85
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 );
90
90
}
91
91
92
92
@ VisibleForTesting
93
93
void setBotApi (BotApi botApi ) {
94
- this .botApi = botApi ;
94
+ this .clientWrapper . setBotApi ( botApi ) ;
95
95
}
96
96
97
97
@ VisibleForTesting
98
98
void setHealthApi (HealthApi healthApi ) {
99
- this .healthApi = healthApi ;
99
+ this .clientWrapper . setHealthApi ( healthApi ) ;
100
100
}
101
101
102
102
@ VisibleForTesting
103
103
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 ;
105
110
}
106
111
107
112
@ Override
@@ -112,15 +117,28 @@ public BotResponse startChatSession(RequestConfig config,
112
117
if (!isApiVersionSupported ()) {
113
118
throw new UnsupportedSDKException (getCurrentApiVersion (), getLatestApiVersion ());
114
119
}
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
+
115
129
InitMessageEnvelope initMessageEnvelope = createInitMessageEnvelope (config , sessionId ,
116
130
botSendMessageRequest );
117
131
118
132
notifyRequestEnvelopeInterceptor (botSendMessageRequest , initMessageEnvelope );
119
133
CompletableFuture <BotResponse > futureResponse = invokeEstablishChatSession (config ,
120
134
initMessageEnvelope ,
121
- botSendMessageRequest );
135
+ botSendMessageRequest ,
136
+ clientWrapper );
122
137
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 ;
124
142
} catch (InterruptedException | ExecutionException e ) {
125
143
throw new RuntimeException (e );
126
144
}
@@ -141,11 +159,13 @@ public BotResponse sendMessage(RequestConfig config,
141
159
142
160
ChatMessageEnvelope chatMessageEnvelope = createChatMessageEnvelope (botSendMessageRequest );
143
161
162
+ ClientWrapper clientWrapper = getCachedClientWrapper (sessionId );
144
163
notifyRequestEnvelopeInterceptor (botSendMessageRequest , chatMessageEnvelope );
145
164
CompletableFuture <BotResponse > futureResponse = invokeContinueChatSession (config .getOrgId (),
146
165
sessionId .getValue (),
147
166
chatMessageEnvelope ,
148
- botSendMessageRequest );
167
+ botSendMessageRequest ,
168
+ clientWrapper );
149
169
150
170
try {
151
171
return futureResponse .get ();
@@ -166,11 +186,14 @@ public BotResponse endChatSession(RequestConfig config,
166
186
BotEndSessionRequest botEndSessionRequest ) {
167
187
168
188
EndSessionReason endSessionReason = botEndSessionRequest .getEndSessionReason ();
189
+
190
+ ClientWrapper clientWrapper = getCachedClientWrapper (sessionId );
169
191
notifyRequestEnvelopeInterceptor (botEndSessionRequest , "EndSessionReason: " + endSessionReason );
170
192
CompletableFuture <BotResponse > futureResponse = invokeEndChatSession (config .getOrgId (),
171
193
sessionId .getValue (),
172
194
endSessionReason ,
173
- botEndSessionRequest );
195
+ botEndSessionRequest ,
196
+ clientWrapper );
174
197
try {
175
198
return futureResponse .get ();
176
199
} catch (InterruptedException | ExecutionException e ) {
@@ -183,11 +206,23 @@ protected void notifyRequestEnvelopeInterceptor(BotRequest botRequest, Object re
183
206
.accept (requestEnvelope );
184
207
}
185
208
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
+
186
221
protected CompletableFuture <BotResponse > invokeEndChatSession (String orgId , String sessionId ,
187
- EndSessionReason endSessionReason , BotEndSessionRequest botRequest ) {
222
+ EndSessionReason endSessionReason , BotEndSessionRequest botRequest , ClientWrapper clientWrapper ) {
188
223
189
- apiClient .setBearerToken (authMechanism .getToken ());
190
- CompletableFuture <BotResponse > futureResponse = botApi
224
+ clientWrapper . getApiClient () .setBearerToken (authMechanism .getToken ());
225
+ CompletableFuture <BotResponse > futureResponse = clientWrapper . getBotApi ()
191
226
.endSessionWithHttpInfo (sessionId ,
192
227
orgId ,
193
228
endSessionReason ,
@@ -202,10 +237,11 @@ protected CompletableFuture<BotResponse> invokeEndChatSession(String orgId, Stri
202
237
203
238
protected CompletableFuture <BotResponse > invokeEstablishChatSession (RequestConfig config ,
204
239
InitMessageEnvelope initMessageEnvelope ,
205
- BotSendMessageRequest botRequest ) {
240
+ BotSendMessageRequest botRequest ,
241
+ ClientWrapper clientWrapper ) {
206
242
207
- apiClient .setBearerToken (authMechanism .getToken ());
208
- CompletableFuture <BotResponse > futureResponse = botApi
243
+ clientWrapper . getApiClient () .setBearerToken (authMechanism .getToken ());
244
+ CompletableFuture <BotResponse > futureResponse = clientWrapper . getBotApi ()
209
245
.startSessionWithHttpInfo (config .getBotId (), config .getOrgId (),
210
246
initMessageEnvelope , botRequest .getRequestId ().orElse (null ))
211
247
.toFuture ()
@@ -216,10 +252,11 @@ protected CompletableFuture<BotResponse> invokeEstablishChatSession(RequestConfi
216
252
217
253
protected CompletableFuture <BotResponse > invokeContinueChatSession (String orgId , String sessionId ,
218
254
ChatMessageEnvelope messageEnvelope ,
219
- BotSendMessageRequest botRequest ) {
255
+ BotSendMessageRequest botRequest ,
256
+ ClientWrapper clientWrapper ) {
220
257
221
- apiClient .setBearerToken (authMechanism .getToken ());
222
- CompletableFuture <BotResponse > futureResponse = botApi
258
+ clientWrapper . getApiClient () .setBearerToken (authMechanism .getToken ());
259
+ CompletableFuture <BotResponse > futureResponse = clientWrapper . getBotApi ()
223
260
.continueSessionWithHttpInfo (sessionId ,
224
261
orgId ,
225
262
messageEnvelope ,
@@ -233,7 +270,7 @@ protected CompletableFuture<BotResponse> invokeContinueChatSession(String orgId,
233
270
}
234
271
235
272
public Status getHealthStatus () {
236
- CompletableFuture <Status > statusFuture = healthApi .checkHealthStatus ().toFuture ();
273
+ CompletableFuture <Status > statusFuture = this . clientWrapper . getHealthApi () .checkHealthStatus ().toFuture ();
237
274
238
275
try {
239
276
return statusFuture .get ();
@@ -243,7 +280,7 @@ public Status getHealthStatus() {
243
280
}
244
281
245
282
public SupportedVersions getSupportedVersions () {
246
- CompletableFuture <SupportedVersions > versionsFuture = versionsApi .getAPIVersions ().toFuture ();
283
+ CompletableFuture <SupportedVersions > versionsFuture = this . clientWrapper . getVersionsApi () .getAPIVersions ().toFuture ();
247
284
248
285
try {
249
286
SupportedVersions versions = versionsFuture .get ();
@@ -256,30 +293,28 @@ public SupportedVersions getSupportedVersions() {
256
293
}
257
294
}
258
295
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
+ }
283
318
}
284
319
285
320
private String getCurrentApiVersion () {
0 commit comments