Skip to content

Commit 6f5fd48

Browse files
committed
refactor namings
1 parent bf955f0 commit 6f5fd48

File tree

6 files changed

+129
-38
lines changed

6 files changed

+129
-38
lines changed

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

Lines changed: 2 additions & 15 deletions
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 UserServiceFactory(retrofit);
79+
this.serviceFactory = new UserServiceFactoryProxy(retrofit);
8080
}
8181

8282
private Retrofit buildRetrofitClient() {
@@ -159,20 +159,7 @@ public <TService> TService create(Class<TService> svcClass) {
159159
}
160160

161161
public <TService> @NotNull TService create2(Class<TService> svcClass, String userToken) {
162-
// Create a tagged retrofit instance with a Call.Factory that tags all requests
163-
164-
okhttp3.Call.Factory taggingFactory = request -> {
165-
Request taggedRequest = request.newBuilder()
166-
.tag(UserToken.class, new UserToken(userToken))
167-
.build();
168-
return okHttpClient.newCall(taggedRequest);
169-
};
170-
171-
Retrofit taggedRetrofit = retrofit.newBuilder()
172-
.callFactory(taggingFactory)
173-
.build();
174-
175-
return taggedRetrofit.create(svcClass);
162+
return new UserServiceFactoryTagging(retrofit).create(svcClass, new UserToken(userToken));
176163
}
177164

178165
@NotNull
Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,7 @@
11
package io.getstream.chat.java.services.framework;
22

3-
import retrofit2.Retrofit;
3+
public interface UserServiceFactory {
44

5-
import static java.lang.reflect.Proxy.newProxyInstance;
6-
7-
class UserServiceFactory {
8-
9-
private final Retrofit retrofit;
10-
11-
public UserServiceFactory(Retrofit retrofit) {
12-
this.retrofit = retrofit;
13-
}
14-
15-
@SuppressWarnings("unchecked")
16-
public final <TService> TService create(Class<TService> svcClass, UserToken userToken) {
17-
return (TService) newProxyInstance(
18-
svcClass.getClassLoader(),
19-
new Class<?>[] { svcClass },
20-
new UserTokenCallProxy<>(retrofit.callFactory(), retrofit.create(svcClass), userToken)
21-
);
22-
}
5+
<TService> TService create(Class<TService> svcClass, UserToken userToken);
236

247
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.getstream.chat.java.services.framework;
2+
3+
import retrofit2.Retrofit;
4+
5+
import static java.lang.reflect.Proxy.newProxyInstance;
6+
7+
/**
8+
* User-aware service factory that uses dynamic proxies to inject user tokens.
9+
* <p>
10+
* This implementation wraps Retrofit service interfaces with a dynamic proxy that intercepts
11+
* method calls and delegates to {@link UserTokenCallRewriter} for token injection.
12+
* <p>
13+
* <b>Mechanism:</b> Uses Java reflection {@link java.lang.reflect.Proxy} to wrap the service
14+
* interface and inject user tokens at method invocation time.
15+
* <p>
16+
* <b>Trade-offs:</b>
17+
* <ul>
18+
* <li>Pros: Reuses single Retrofit instance, flexible interception point</li>
19+
* <li>Cons: Reflection overhead on every method call, slightly more complex debugging</li>
20+
* </ul>
21+
* <p>
22+
* <b>Thread-safety:</b> Immutable and thread-safe once constructed. The underlying proxy
23+
* delegation is also thread-safe.
24+
*
25+
* @see UserTokenCallRewriter
26+
*/
27+
final class UserServiceFactoryProxy implements UserServiceFactory {
28+
29+
private final Retrofit retrofit;
30+
31+
/**
32+
* Constructs a new proxy-based user service factory.
33+
*
34+
* @param retrofit the Retrofit instance to create services from
35+
*/
36+
public UserServiceFactoryProxy(Retrofit retrofit) {
37+
this.retrofit = retrofit;
38+
}
39+
40+
/**
41+
* Creates a user-aware service instance using a dynamic proxy.
42+
* <p>
43+
* The returned service is a dynamic proxy that intercepts all method calls and delegates
44+
* to the underlying Retrofit service while injecting the user token.
45+
*
46+
* @param svcClass the Retrofit service interface class
47+
* @param userToken the user token to inject into all requests from this service
48+
* @param <TService> the service type
49+
* @return a proxied service instance that injects the user token
50+
*/
51+
@SuppressWarnings("unchecked")
52+
public final <TService> TService create(Class<TService> svcClass, UserToken userToken) {
53+
return (TService) newProxyInstance(
54+
svcClass.getClassLoader(),
55+
new Class<?>[] { svcClass },
56+
new UserTokenCallRewriter<>(retrofit.callFactory(), retrofit.create(svcClass), userToken)
57+
);
58+
}
59+
60+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package io.getstream.chat.java.services.framework;
2+
3+
import okhttp3.Request;
4+
import retrofit2.Retrofit;
5+
6+
/**
7+
* User-aware service factory that tags OkHttp requests with user tokens.
8+
* <p>
9+
* This implementation wraps the OkHttp call factory to automatically attach a {@link UserToken}
10+
* as a request tag. The token can then be retrieved by interceptors for authentication purposes.
11+
* <p>
12+
* <b>Mechanism:</b> Creates a new Retrofit instance with a custom call factory that tags
13+
* each request before delegating to the underlying call factory.
14+
* <p>
15+
* <b>Trade-offs:</b>
16+
* <ul>
17+
* <li>Pros: Clean, type-safe, works with any Retrofit service, no reflection overhead</li>
18+
* <li>Cons: Creates a new Retrofit instance per service call (minor memory overhead)</li>
19+
* </ul>
20+
* <p>
21+
* <b>Thread-safety:</b> Immutable and thread-safe once constructed.
22+
*/
23+
final class UserServiceFactoryTagging implements UserServiceFactory {
24+
25+
private final Retrofit retrofit;
26+
27+
/**
28+
* Constructs a new tagging-based user service factory.
29+
*
30+
* @param retrofit the base Retrofit instance to derive user-specific instances from
31+
*/
32+
public UserServiceFactoryTagging(Retrofit retrofit) {
33+
this.retrofit = retrofit;
34+
}
35+
36+
/**
37+
* Creates a user-aware service instance that automatically tags requests with the user token.
38+
* <p>
39+
* The returned service wraps each OkHttp request with a {@link UserToken} tag that can be
40+
* retrieved by interceptors using {@code request.tag(UserToken.class)}.
41+
*
42+
* @param svcClass the Retrofit service interface class
43+
* @param userToken the user token to attach to all requests from this service
44+
* @param <TService> the service type
45+
* @return a service instance that tags requests with the user token
46+
*/
47+
@SuppressWarnings("unchecked")
48+
public final <TService> TService create(Class<TService> svcClass, UserToken userToken) {
49+
Retrofit taggedRetrofit = retrofit.newBuilder()
50+
.callFactory(request -> {
51+
Request taggedRequest = request.newBuilder()
52+
.tag(UserToken.class, userToken)
53+
.build();
54+
return retrofit.callFactory().newCall(taggedRequest);
55+
})
56+
.build();
57+
58+
return taggedRetrofit.create(svcClass);
59+
}
60+
61+
}

src/main/java/io/getstream/chat/java/services/framework/UserTokenCallProxy.java renamed to src/main/java/io/getstream/chat/java/services/framework/UserTokenCallRewriter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
* This approach allows per-call authentication without creating multiple OkHttpClient
1515
* instances, making it suitable for multi-tenant systems with thousands of users.
1616
*/
17-
class UserTokenCallProxy<TService> implements InvocationHandler {
17+
class UserTokenCallRewriter<TService> implements InvocationHandler {
1818
private static volatile Field rawCallField;
1919

2020
private final Call.Factory callFactory;
2121
private final TService delegate;
2222
private final UserToken token;
2323

24-
UserTokenCallProxy(@NotNull Call.Factory callFactory, @NotNull TService delegate, @NotNull UserToken token) {
24+
UserTokenCallRewriter(@NotNull Call.Factory callFactory, @NotNull TService delegate, @NotNull UserToken token) {
2525
this.callFactory = callFactory;
2626
this.delegate = delegate;
2727
this.token = token;
@@ -45,7 +45,7 @@ private retrofit2.Call<?> injectTokenIntoCall(retrofit2.Call<?> originalCall) {
4545
try {
4646
// Cache field lookup for performance (double-checked locking)
4747
if (rawCallField == null) {
48-
synchronized (UserTokenCallProxy.class) {
48+
synchronized (UserTokenCallRewriter.class) {
4949
if (rawCallField == null) {
5050
rawCallField = clonedCall.getClass().getDeclaredField("rawCall");
5151
rawCallField.setAccessible(true);

src/test/java/io/getstream/chat/java/CustomTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ void measureClientCreate() throws Exception {
3434
// Test creating a UserClient directly - should use Client-Side auth
3535
var defaultClient = new DefaultClient();
3636

37-
var iterations = 100_000_000;
37+
var iterations = 30_000_000;
3838

3939
// Warm up JVM to avoid cold start effects
4040
for (int i = 0; i < 10_000; i++) {

0 commit comments

Comments
 (0)