Skip to content

Commit e35ff3e

Browse files
committed
add UserServiceFactorySelector & fix visibility
1 parent 2d7ca90 commit e35ff3e

File tree

7 files changed

+99
-12
lines changed

7 files changed

+99
-12
lines changed

src/main/java/io/getstream/chat/java/services/framework/DefaultClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public DefaultClient(Properties properties) {
7676
this.apiSecret = apiSecret.toString();
7777
this.apiKey = apiKey.toString();
7878
this.retrofit = buildRetrofitClient();
79-
this.serviceFactory = new UserServiceFactoryProxy(retrofit);
79+
this.serviceFactory = new UserServiceFactorySelector(retrofit);
8080
}
8181

8282
private Retrofit buildRetrofitClient() {

src/main/java/io/getstream/chat/java/services/framework/UserCall.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import java.io.IOException;
77

8-
public class UserCall<T> implements retrofit2.Call<T> {
8+
class UserCall<T> implements retrofit2.Call<T> {
99
private final retrofit2.Call<T> delegate;
1010
private final UserToken token;
1111

src/main/java/io/getstream/chat/java/services/framework/UserServiceFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.getstream.chat.java.services.framework;
22

3-
public interface UserServiceFactory {
3+
interface UserServiceFactory {
44

55
<TService> TService create(Class<TService> svcClass, UserToken userToken);
66

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package io.getstream.chat.java.services.framework;
2+
3+
import retrofit2.Retrofit;
4+
import java.util.concurrent.atomic.AtomicReference;
5+
6+
/**
7+
* Smart user-aware service factory with automatic fallback mechanism.
8+
* <p>
9+
* This implementation attempts to use {@link UserServiceFactoryProxy} (more efficient)
10+
* and automatically falls back to {@link UserServiceFactoryTagging} if reflection fails
11+
* or the Retrofit API has changed.
12+
* <p>
13+
* <b>Fallback Strategy:</b>
14+
* <ol>
15+
* <li>First attempt: Use proxy-based approach (reuses Retrofit instance)</li>
16+
* <li>On failure: Switch to tagging-based approach (more compatible)</li>
17+
* <li>Once switched: All subsequent calls use the fallback implementation</li>
18+
* </ol>
19+
* <p>
20+
* <b>Thread-safety:</b> Thread-safe. Uses atomic reference for fallback state tracking.
21+
* Multiple threads may attempt fallback simultaneously, but only one will win.
22+
*/
23+
final class UserServiceFactorySelector implements UserServiceFactory {
24+
25+
private final UserServiceFactory proxyFactory;
26+
private final UserServiceFactory taggingFactory;
27+
private final AtomicReference<UserServiceFactory> activeFactory;
28+
29+
/**
30+
* Constructs a new smart factory with fallback capability.
31+
*
32+
* @param retrofit the Retrofit instance to create services from
33+
*/
34+
public UserServiceFactorySelector(Retrofit retrofit) {
35+
this.proxyFactory = new UserServiceFactoryProxy(retrofit);
36+
this.taggingFactory = new UserServiceFactoryTagging(retrofit);
37+
38+
// Verify proxy approach is viable before setting default
39+
UserServiceFactory defaultFactory = proxyFactory;
40+
try {
41+
// Check if we can access the rawCall field that UserTokenCallRewriter needs
42+
Class<?> retrofitCallClass = Class.forName("retrofit2.OkHttpCall");
43+
retrofitCallClass.getDeclaredField("rawCall");
44+
// If we get here, proxy should work
45+
} catch (Throwable e) {
46+
// Proxy approach won't work, use tagging as default
47+
defaultFactory = taggingFactory;
48+
}
49+
50+
this.activeFactory = new AtomicReference<>(defaultFactory);
51+
}
52+
53+
/**
54+
* Creates a user-aware service instance with automatic fallback.
55+
* <p>
56+
* Attempts to use the proxy implementation first. If it fails (due to reflection issues,
57+
* API changes, or other errors), automatically switches to the tagging implementation
58+
* and retries.
59+
*
60+
* @param svcClass the Retrofit service interface class
61+
* @param userToken the user token to inject into all requests from this service
62+
* @param <TService> the service type
63+
* @return a service instance that injects the user token
64+
* @throws RuntimeException if both implementations fail
65+
*/
66+
@Override
67+
public <TService> TService create(Class<TService> svcClass, UserToken userToken) {
68+
UserServiceFactory factory = activeFactory.get();
69+
70+
try {
71+
return factory.create(svcClass, userToken);
72+
} catch (Throwable e) {
73+
// If we're already using the fallback, propagate the error
74+
if (factory == taggingFactory) {
75+
throw new RuntimeException("Failed to create service using fallback implementation", e);
76+
}
77+
78+
// Switch to fallback and retry
79+
activeFactory.compareAndSet(proxyFactory, taggingFactory);
80+
81+
// Retry with fallback
82+
try {
83+
return taggingFactory.create(svcClass, userToken);
84+
} catch (Throwable fallbackException) {
85+
throw new RuntimeException("Failed to create service with both implementations", fallbackException);
86+
}
87+
}
88+
}
89+
90+
}
91+

src/main/java/io/getstream/chat/java/services/framework/UserServiceFactoryTagging.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ final class UserServiceFactoryTagging implements UserServiceFactory {
2929
*
3030
* @param retrofit the base Retrofit instance to derive user-specific instances from
3131
*/
32-
public UserServiceFactoryTagging(Retrofit retrofit) {
32+
UserServiceFactoryTagging(Retrofit retrofit) {
3333
this.retrofit = retrofit;
3434
}
3535

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
package io.getstream.chat.java.services.framework;
22

3-
public final class UserToken {
3+
final class UserToken {
44
private final String value;
55

6-
public UserToken(String value) {
6+
UserToken(String value) {
77
this.value = value;
88
}
99

10-
public String value() {
10+
String value() {
1111
return value;
1212
}
13-
14-
public boolean isBlank() {
15-
return value == null || value.isBlank();
16-
}
1713
}

src/main/java/io/getstream/chat/java/services/framework/internal/TokenInjectionException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* - A service method doesn't return retrofit2.Call<?>
77
* - Retrofit's internal structure changes and reflection fails
88
*/
9-
public class TokenInjectionException extends Exception {
9+
public class TokenInjectionException extends ReflectiveOperationException {
1010
public TokenInjectionException(String message) {
1111
super(message);
1212
}

0 commit comments

Comments
 (0)