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 ;
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 ;
58
57
import org .springframework .web .reactive .function .client .WebClient ;
59
- import reactor .core .publisher .Mono ;
60
58
61
59
/**
62
60
* This is a basic implementation of {@link BasicChatbotClient}. It does not perform session
67
65
*/
68
66
public class BasicChatbotClientImpl implements BasicChatbotClient {
69
67
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 ;
74
75
protected AuthMechanism authMechanism ;
75
- protected ReleaseInfo releaseInfo = ReleaseInfo . getInstance () ;
76
+ protected ClientWrapper clientWrapper ;
76
77
77
78
protected BasicChatbotClientImpl (String basePath ,
78
79
AuthMechanism authMechanism ,
79
80
WebClient .Builder webClientBuilder ) {
80
81
81
82
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 );
90
87
}
91
88
92
89
@ VisibleForTesting
93
90
void setBotApi (BotApi botApi ) {
94
- this .botApi = botApi ;
91
+ this .clientWrapper . setBotApi ( botApi ) ;
95
92
}
96
93
97
94
@ VisibleForTesting
98
95
void setHealthApi (HealthApi healthApi ) {
99
- this .healthApi = healthApi ;
96
+ this .clientWrapper . setHealthApi ( healthApi ) ;
100
97
}
101
98
102
99
@ VisibleForTesting
103
100
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 ;
105
107
}
106
108
107
109
@ Override
@@ -112,15 +114,28 @@ public BotResponse startChatSession(RequestConfig config,
112
114
if (!isApiVersionSupported ()) {
113
115
throw new UnsupportedSDKException (getCurrentApiVersion (), getLatestApiVersion ());
114
116
}
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
+
115
126
InitMessageEnvelope initMessageEnvelope = createInitMessageEnvelope (config , sessionId ,
116
127
botSendMessageRequest );
117
128
118
129
notifyRequestEnvelopeInterceptor (botSendMessageRequest , initMessageEnvelope );
119
130
CompletableFuture <BotResponse > futureResponse = invokeEstablishChatSession (config ,
120
131
initMessageEnvelope ,
121
- botSendMessageRequest );
132
+ botSendMessageRequest ,
133
+ clientWrapper );
122
134
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 ;
124
139
} catch (InterruptedException | ExecutionException e ) {
125
140
throw new RuntimeException (e );
126
141
}
@@ -141,11 +156,13 @@ public BotResponse sendMessage(RequestConfig config,
141
156
142
157
ChatMessageEnvelope chatMessageEnvelope = createChatMessageEnvelope (botSendMessageRequest );
143
158
159
+ ClientWrapper clientWrapper = getCachedClientWrapper (sessionId );
144
160
notifyRequestEnvelopeInterceptor (botSendMessageRequest , chatMessageEnvelope );
145
161
CompletableFuture <BotResponse > futureResponse = invokeContinueChatSession (config .getOrgId (),
146
162
sessionId .getValue (),
147
163
chatMessageEnvelope ,
148
- botSendMessageRequest );
164
+ botSendMessageRequest ,
165
+ clientWrapper );
149
166
150
167
try {
151
168
return futureResponse .get ();
@@ -166,11 +183,14 @@ public BotResponse endChatSession(RequestConfig config,
166
183
BotEndSessionRequest botEndSessionRequest ) {
167
184
168
185
EndSessionReason endSessionReason = botEndSessionRequest .getEndSessionReason ();
186
+
187
+ ClientWrapper clientWrapper = getCachedClientWrapper (sessionId );
169
188
notifyRequestEnvelopeInterceptor (botEndSessionRequest , "EndSessionReason: " + endSessionReason );
170
189
CompletableFuture <BotResponse > futureResponse = invokeEndChatSession (config .getOrgId (),
171
190
sessionId .getValue (),
172
191
endSessionReason ,
173
- botEndSessionRequest );
192
+ botEndSessionRequest ,
193
+ clientWrapper );
174
194
try {
175
195
return futureResponse .get ();
176
196
} catch (InterruptedException | ExecutionException e ) {
@@ -183,11 +203,23 @@ protected void notifyRequestEnvelopeInterceptor(BotRequest botRequest, Object re
183
203
.accept (requestEnvelope );
184
204
}
185
205
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
+
186
218
protected CompletableFuture <BotResponse > invokeEndChatSession (String orgId , String sessionId ,
187
- EndSessionReason endSessionReason , BotEndSessionRequest botRequest ) {
219
+ EndSessionReason endSessionReason , BotEndSessionRequest botRequest , ClientWrapper clientWrapper ) {
188
220
189
- apiClient .setBearerToken (authMechanism .getToken ());
190
- CompletableFuture <BotResponse > futureResponse = botApi
221
+ clientWrapper . getApiClient () .setBearerToken (authMechanism .getToken ());
222
+ CompletableFuture <BotResponse > futureResponse = clientWrapper . getBotApi ()
191
223
.endSessionWithHttpInfo (sessionId ,
192
224
orgId ,
193
225
endSessionReason ,
@@ -202,10 +234,11 @@ protected CompletableFuture<BotResponse> invokeEndChatSession(String orgId, Stri
202
234
203
235
protected CompletableFuture <BotResponse > invokeEstablishChatSession (RequestConfig config ,
204
236
InitMessageEnvelope initMessageEnvelope ,
205
- BotSendMessageRequest botRequest ) {
237
+ BotSendMessageRequest botRequest ,
238
+ ClientWrapper clientWrapper ) {
206
239
207
- apiClient .setBearerToken (authMechanism .getToken ());
208
- CompletableFuture <BotResponse > futureResponse = botApi
240
+ clientWrapper . getApiClient () .setBearerToken (authMechanism .getToken ());
241
+ CompletableFuture <BotResponse > futureResponse = clientWrapper . getBotApi ()
209
242
.startSessionWithHttpInfo (config .getBotId (), config .getOrgId (),
210
243
initMessageEnvelope , botRequest .getRequestId ().orElse (null ))
211
244
.toFuture ()
@@ -216,10 +249,11 @@ protected CompletableFuture<BotResponse> invokeEstablishChatSession(RequestConfi
216
249
217
250
protected CompletableFuture <BotResponse > invokeContinueChatSession (String orgId , String sessionId ,
218
251
ChatMessageEnvelope messageEnvelope ,
219
- BotSendMessageRequest botRequest ) {
252
+ BotSendMessageRequest botRequest ,
253
+ ClientWrapper clientWrapper ) {
220
254
221
- apiClient .setBearerToken (authMechanism .getToken ());
222
- CompletableFuture <BotResponse > futureResponse = botApi
255
+ clientWrapper . getApiClient () .setBearerToken (authMechanism .getToken ());
256
+ CompletableFuture <BotResponse > futureResponse = clientWrapper . getBotApi ()
223
257
.continueSessionWithHttpInfo (sessionId ,
224
258
orgId ,
225
259
messageEnvelope ,
@@ -233,7 +267,7 @@ protected CompletableFuture<BotResponse> invokeContinueChatSession(String orgId,
233
267
}
234
268
235
269
public Status getHealthStatus () {
236
- CompletableFuture <Status > statusFuture = healthApi .checkHealthStatus ().toFuture ();
270
+ CompletableFuture <Status > statusFuture = this . clientWrapper . getHealthApi () .checkHealthStatus ().toFuture ();
237
271
238
272
try {
239
273
return statusFuture .get ();
@@ -243,7 +277,7 @@ public Status getHealthStatus() {
243
277
}
244
278
245
279
public SupportedVersions getSupportedVersions () {
246
- CompletableFuture <SupportedVersions > versionsFuture = versionsApi .getAPIVersions ().toFuture ();
280
+ CompletableFuture <SupportedVersions > versionsFuture = this . clientWrapper . getVersionsApi () .getAPIVersions ().toFuture ();
247
281
248
282
try {
249
283
SupportedVersions versions = versionsFuture .get ();
@@ -256,30 +290,23 @@ public SupportedVersions getSupportedVersions() {
256
290
}
257
291
}
258
292
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
+ }
283
310
}
284
311
285
312
private String getCurrentApiVersion () {
0 commit comments