3535import retrofit2 .http .Headers ;
3636import retrofit2 .http .*;
3737
38- import javax .annotation .CheckForNull ;
38+ import javax .annotation .* ;
3939import javax .net .ssl .*;
4040import java .io .*;
4141import java .security .*;
4242import java .security .cert .X509Certificate ;
4343import java .util .*;
4444import java .util .concurrent .TimeUnit ;
45+ import java .util .concurrent .atomic .AtomicBoolean ;
4546import java .util .function .*;
4647
47- public class RestAnalyticsProvider implements AnalyticsProvider , Closeable {
48+ public class RestAnalyticsProvider implements AnalyticsProvider , Closeable , BaseUrlProvider . UrlChangedListener {
4849
4950 public static final ThreadLocal <Long > PERFORMANCE = new ThreadLocal <>();
51+ private final List <AuthenticationProvider > authenticationProviders ;
52+ private final Consumer <String > logger ;
5053
51- private final Client client ;
52- private final String apiUrl ;
54+ private Client client ;
55+ private final BaseUrlProvider baseUrlProvider ;
56+ private final AtomicBoolean replacingClient = new AtomicBoolean (false );
57+ private final Object replacingClientLock = new Object ();
5358
5459 //this constructor is used only in tests
5560 RestAnalyticsProvider (String baseUrl ) {
5661
57- this (baseUrl , Collections .singletonList (new AuthenticationProvider () {
62+ this (Collections .singletonList (new AuthenticationProvider () {
5863 @ CheckForNull
5964 @ Override
6065 public String getHeaderName () {
6166 return null ;
6267 }
68+
6369 @ CheckForNull
6470 @ Override
6571 public String getHeaderValue () {
6672 return null ;
6773 }
68- }), System .out ::println );
74+ }), System .out ::println , new BaseUrlProvider () {
75+ @ Nonnull
76+ @ Override
77+ public String baseUrl () {
78+ return baseUrl ;
79+ }
80+
81+ @ Override
82+ public void addUrlChangedListener (UrlChangedListener urlChangedListener , int order ) {
83+
84+ }
85+
86+ @ Override
87+ public void removeUrlChangedListener (UrlChangedListener urlChangedListener ) {
88+
89+ }
90+ }, 1 );
91+ }
92+
93+ public RestAnalyticsProvider (List <AuthenticationProvider > authenticationProviders , Consumer <String > logger , BaseUrlProvider baseUrlProvider , int urlChangeOrder ) {
94+ this .authenticationProviders = authenticationProviders ;
95+ this .logger = logger ;
96+ this .baseUrlProvider = baseUrlProvider ;
97+ this .client = constructClient (authenticationProviders , logger , baseUrlProvider );
98+ baseUrlProvider .addUrlChangedListener (this , urlChangeOrder );
99+ }
100+
101+
102+ private Client constructClient (List <AuthenticationProvider > authenticationProviders , Consumer <String > logger , BaseUrlProvider baseUrlProvider ) {
103+ try {
104+ return createClient (authenticationProviders , logger , baseUrlProvider );
105+ } catch (Throwable e ) {
106+ throw new CantConstructClientException (e );
107+ }
69108 }
70109
71- public RestAnalyticsProvider (String baseUrl , List <AuthenticationProvider > authenticationProviders , Consumer <String > logger ) {
72- this .client = createClient (baseUrl , authenticationProviders , logger );
73- this .apiUrl = baseUrl ;
110+
111+ @ Override
112+ public void urlChanged (BaseUrlProvider .UrlChangedEvent urlChangedEvent ) {
113+ synchronized (replacingClientLock ) {
114+ try {
115+ replacingClient .set (true );
116+ client .close ();
117+ client = constructClient (authenticationProviders , logger , baseUrlProvider );
118+ } finally {
119+ replacingClient .set (false );
120+ }
121+ }
74122 }
75123
124+
76125 public String getApiUrl () {
77- return apiUrl ;
126+ return baseUrlProvider . baseUrl () ;
78127 }
79128
80129 @ Override
@@ -360,7 +409,7 @@ public String getSpanInfo(String spanCodeObjectId) {
360409
361410 @ Override
362411 public void resetThrottlingStatus () {
363- execute (() -> client .analyticsProvider . resetThrottlingStatus () );
412+ execute (client .analyticsProvider :: resetThrottlingStatus );
364413 }
365414
366415 @ Override
@@ -400,9 +449,10 @@ private HttpResponse toHttpResponse(okhttp3.Response response) {
400449 var body = response .body ();
401450 var headers = response .headers ().toMultimap ().entrySet ().stream ()
402451 .filter (i -> !i .getValue ().isEmpty ())
403- .collect (HashMap <String , String >::new , (m ,i ) -> m .put (i .getKey (), i .getValue ().get (0 )), Map ::putAll );
452+ .collect (HashMap <String , String >::new , (m , i ) -> m .put (i .getKey (), i .getValue ().get (0 )), Map ::putAll );
404453 var contentLength = body != null ? body .contentLength () : null ;
405- var contentType = body != null && body .contentType () != null ? body .contentType ().toString () : null ;
454+ MediaType mediaType = body != null ? body .contentType () : null ;
455+ var contentType = mediaType != null ? mediaType .toString () : null ;
406456 var contentStream = body != null ? body .byteStream () : null ;
407457
408458 return new HttpResponse (
@@ -428,6 +478,12 @@ protected static String readEntire(ResponseBody responseBody) {
428478
429479 public <T > T execute (Supplier <Call <T >> supplier ) {
430480
481+ //don't want to use locks, just throw calling threads if currently replacing the client as a result of url changed.
482+ //caller should always catch and handle AnalyticsProviderException
483+ if (replacingClient .get ()) {
484+ throw new ReplacingClientException ("can't serve requests, currently replacing clients" );
485+ }
486+
431487 Response <T > response ;
432488 try {
433489 Call <T > call = supplier .get ();
@@ -450,7 +506,7 @@ public <T> T execute(Supplier<Call<T>> supplier) {
450506 private AnalyticsProviderException createUnsuccessfulResponseException (int code , ResponseBody errorBody ) throws IOException {
451507 var errorMessage = errorBody == null ? null : errorBody .string ();
452508 if (code == HTTPConstants .UNAUTHORIZED ) {
453- var message = errorMessage != null && !errorMessage .isEmpty () ? errorMessage : "Unauthorized " + code ;
509+ var message = errorMessage != null && !errorMessage .isEmpty () ? errorMessage : "Unauthorized " + code ;
454510 return new AuthenticationException (code , message );
455511 }
456512
@@ -459,14 +515,15 @@ private AnalyticsProviderException createUnsuccessfulResponseException(int code,
459515 }
460516
461517
462- private Client createClient (String baseUrl , List <AuthenticationProvider > authenticationProviders , Consumer <String > logger ) {
463- return new Client (baseUrl , authenticationProviders , logger );
518+ private Client createClient (List <AuthenticationProvider > authenticationProviders , Consumer <String > logger , BaseUrlProvider baseUrlProvider ) {
519+ return new Client (authenticationProviders , logger , baseUrlProvider );
464520 }
465521
466522
467523 @ Override
468524 public void close () throws IOException {
469525 client .close ();
526+ baseUrlProvider .removeUrlChangedListener (this );
470527 }
471528
472529
@@ -478,15 +535,17 @@ private static class Client implements Closeable {
478535 private final OkHttpClient okHttpClient ;
479536
480537 @ SuppressWarnings ("MoveFieldAssignmentToInitializer" )
481- public Client (String baseUrl , List <AuthenticationProvider > authenticationProviders , Consumer <String > logger ) {
538+ public Client (List <AuthenticationProvider > authenticationProviders , Consumer <String > logger , BaseUrlProvider baseUrlProvider ) {
482539
483540 //configure okHttp here if necessary
484541 OkHttpClient .Builder builder = new OkHttpClient .Builder ();
485542
486- if (baseUrl .startsWith ("https:" )) {
487- // SSL
488- applyInsecureSsl (builder );
489- }
543+
544+ //if (baseUrlProvider.baseUrl().startsWith("https:")) {
545+ // SSL
546+ //we can always applyInsecureSsl even if the schema is http
547+ applyInsecureSsl (builder );
548+ //}
490549
491550
492551 authenticationProviders .forEach (authenticationProvider -> builder .addInterceptor (chain -> {
@@ -506,18 +565,16 @@ public Client(String baseUrl, List<AuthenticationProvider> authenticationProvide
506565 //always add the logging interceptor last, so it will log info from all other interceptors
507566 addLoggingInterceptor (builder , logger );
508567
509- builder .callTimeout (10 , TimeUnit .SECONDS )
510- .connectTimeout (5 , TimeUnit .SECONDS )
511- .readTimeout (5 , TimeUnit .SECONDS );
568+ builder .callTimeout (20 , TimeUnit .SECONDS )
569+ .connectTimeout (10 , TimeUnit .SECONDS )
570+ .readTimeout (10 , TimeUnit .SECONDS );
512571
513572 okHttpClient = builder .build ();
514573
515574 var jacksonFactory = JacksonConverterFactory .create (createObjectMapper ());
516575
517- baseUrl = ensureEndsWithSlash (baseUrl );
518-
519576 Retrofit retrofit = new Retrofit .Builder ()
520- .baseUrl (baseUrl )
577+ .baseUrl (ensureEndsWithSlash ( baseUrlProvider . baseUrl ()) )
521578 .client (okHttpClient )
522579 //ScalarsConverterFactory must be the first, it supports serializing to plain String, see getAssets
523580 .addConverterFactory (ScalarsConverterFactory .create ())
@@ -528,17 +585,19 @@ public Client(String baseUrl, List<AuthenticationProvider> authenticationProvide
528585 analyticsProvider = retrofit .create (AnalyticsProviderRetrofit .class );
529586 }
530587
588+
531589 private String ensureEndsWithSlash (String baseUrl ) {
532590 //if url contains path it must end with slash.
533591 //retrofit will check that in retrofit2.Retrofit.Builder.baseUrl(okhttp3.HttpUrl)
534592 var url = HttpUrl .get (baseUrl );
535593 List <String > pathSegments = url .pathSegments ();
536- if (!"" .equals (pathSegments .get (pathSegments .size () - 1 ))) {
594+ if (!pathSegments . isEmpty () && ! "" .equals (pathSegments .get (pathSegments .size () - 1 ))) {
537595 return baseUrl + "/" ;
538596 }
539597 return baseUrl ;
540598 }
541599
600+
542601 private void addPerformanceInterceptor (OkHttpClient .Builder builder ) {
543602 builder .addInterceptor (chain -> {
544603 PERFORMANCE .remove ();
@@ -727,7 +786,7 @@ private interface AnalyticsProviderRetrofit {
727786 "Content-Type:application/json"
728787 })
729788 @ PUT ("CodeAnalytics/insights/start-time" )
730- Call <ResponseBody > setInsightCustomStartTime (
789+ Call <Void > setInsightCustomStartTime (
731790 @ Body CustomStartTimeInsightRequest customStartTimeInsightRequest
732791 );
733792
@@ -803,7 +862,7 @@ Call<ResponseBody> setInsightCustomStartTime(
803862 "Content-Type:application/json"
804863 })
805864 @ POST ("Notifications/read" )
806- Call <ResponseBody > setReadNotificationsTime (@ Body SetReadNotificationsRequest setReadNotificationsRequest );
865+ Call <Void > setReadNotificationsTime (@ Body SetReadNotificationsRequest setReadNotificationsRequest );
807866
808867 @ Headers ({
809868 "Accept: application/+json" ,
@@ -930,28 +989,28 @@ Call<ResponseBody> setInsightCustomStartTime(
930989 "Content-Type:application/json"
931990 })
932991 @ POST ("Insights/markRead" )
933- Call <ResponseBody > markInsightsAsRead (@ Body MarkInsightsAsReadRequest request );
992+ Call <Void > markInsightsAsRead (@ Body MarkInsightsAsReadRequest request );
934993
935994 @ Headers ({
936995 "Accept: application/+json" ,
937996 "Content-Type:application/json"
938997 })
939998 @ POST ("Insights/markAllRead" )
940- Call <ResponseBody > markAllInsightsAsRead (@ Body MarkAllInsightsAsReadRequest request );
999+ Call <Void > markAllInsightsAsRead (@ Body MarkAllInsightsAsReadRequest request );
9411000
9421001 @ Headers ({
9431002 "Accept: application/+json" ,
9441003 "Content-Type:application/json"
9451004 })
9461005 @ PUT ("InsightsActions/dismiss" )
947- Call <ResponseBody > dismissInsight (@ Body DismissRequest insightId );
1006+ Call <Void > dismissInsight (@ Body DismissRequest insightId );
9481007
9491008 @ Headers ({
9501009 "Accept: application/+json" ,
9511010 "Content-Type:application/json"
9521011 })
9531012 @ PUT ("InsightsActions/unDismiss" )
954- Call <ResponseBody > undismissInsight (@ Body UnDismissRequest insightId );
1013+ Call <Void > undismissInsight (@ Body UnDismissRequest insightId );
9551014
9561015 @ Headers ({
9571016 "Accept: application/+json" ,
@@ -1041,11 +1100,12 @@ Call<ResponseBody> setInsightCustomStartTime(
10411100
10421101 @ GET ("spans/info" )
10431102 Call <String > getSpanInfo (@ Query ("SpanCodeObjectId" ) String spanCodeObjectId );
1103+
10441104 @ Headers ({
10451105 "Accept: application/+json" ,
10461106 "Content-Type:application/json"
10471107 })
10481108 @ POST ("PerformanceMetrics/reset-throttling" )
1049- Call <ResponseBody > resetThrottlingStatus ();
1109+ Call <Void > resetThrottlingStatus ();
10501110 }
10511111}
0 commit comments