Skip to content

Commit 80df7ab

Browse files
committed
add docs
1 parent e35ff3e commit 80df7ab

File tree

8 files changed

+198
-35
lines changed

8 files changed

+198
-35
lines changed

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,104 @@
55

66
import java.io.IOException;
77

8+
/**
9+
* Wrapper for Retrofit {@code Call} objects that injects user authentication tokens.
10+
* <p>
11+
* This class delegates all {@code Call} operations to an underlying call while ensuring
12+
* that the {@link UserToken} is attached to the request as a typed tag. The token can
13+
* then be retrieved by OkHttp interceptors for adding authorization headers.
14+
* </p>
15+
*
16+
* @param <T> the response body type
17+
* @see UserToken
18+
* @see UserTokenCallRewriter
19+
*/
820
class UserCall<T> implements retrofit2.Call<T> {
921
private final retrofit2.Call<T> delegate;
1022
private final UserToken token;
1123

24+
/**
25+
* Constructs a new UserCall that wraps the provided call with token injection.
26+
*
27+
* @param delegate the underlying Retrofit call
28+
* @param token the user token to inject
29+
*/
1230
UserCall(retrofit2.Call<T> delegate, UserToken token) {
1331
this.delegate = delegate;
1432
this.token = token;
1533
}
1634

35+
/**
36+
* Executes the HTTP request synchronously.
37+
*
38+
* @return the response
39+
* @throws IOException if the request fails
40+
*/
1741
@Override
1842
public @NotNull retrofit2.Response<T> execute() throws IOException {
1943
return delegate.execute();
2044
}
2145

46+
/**
47+
* Asynchronously sends the request and notifies the callback of its response.
48+
*
49+
* @param callback the callback to notify when the response arrives
50+
*/
2251
@Override
2352
public void enqueue(@NotNull retrofit2.Callback<T> callback) {
2453
delegate.enqueue(callback);
2554
}
2655

56+
/**
57+
* Returns true if this call has been executed.
58+
*
59+
* @return true if executed, false otherwise
60+
*/
2761
@Override
2862
public boolean isExecuted() {
2963
return delegate.isExecuted();
3064
}
3165

66+
/**
67+
* Cancels the request, if possible.
68+
*/
3269
@Override
3370
public void cancel() {
3471
delegate.cancel();
3572
}
3673

74+
/**
75+
* Returns true if this call has been canceled.
76+
*
77+
* @return true if canceled, false otherwise
78+
*/
3779
@Override
3880
public boolean isCanceled() {
3981
return delegate.isCanceled();
4082
}
4183

84+
/**
85+
* Creates a new, identical call that can be executed independently.
86+
* <p>
87+
* The cloned call will also have the user token injected.
88+
* </p>
89+
*
90+
* @return a new call instance
91+
*/
4292
@Override
4393
public @NotNull retrofit2.Call<T> clone() {
4494
return new UserCall<>(delegate.clone(), token);
4595
}
4696

97+
/**
98+
* Returns the original HTTP request with the user token attached as a typed tag.
99+
* <p>
100+
* The token is stored using {@link Request#tag(Class, Object)} and can be retrieved
101+
* by interceptors using {@code request.tag(UserToken.class)}.
102+
* </p>
103+
*
104+
* @return the request with the user token tag
105+
*/
47106
@Override
48107
public @NotNull Request request() {
49108
Request original = delegate.request();
@@ -52,6 +111,11 @@ public boolean isCanceled() {
52111
.build();
53112
}
54113

114+
/**
115+
* Returns the timeout for this call.
116+
*
117+
* @return the timeout
118+
*/
55119
@Override
56120
public @NotNull okio.Timeout timeout() {
57121
return delegate.timeout();

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,70 @@
44

55
import java.time.Duration;
66

7+
/**
8+
* Client implementation for user-scoped API operations.
9+
* <p>
10+
* This client wraps a base {@link Client} and automatically injects a user-specific
11+
* authentication token into all service calls. It's designed for scenarios where
12+
* different users need to make authenticated API calls without creating separate
13+
* client instances per user.
14+
* </p>
15+
*
16+
* @see Client
17+
*/
718
public final class UserClient implements Client {
819

920
private final Client delegate;
1021
private final String userToken;
1122

23+
/**
24+
* Constructs a new UserClient that wraps the provided client with user authentication.
25+
*
26+
* @param delegate the base client to delegate calls to
27+
* @param userToken the user-specific authentication token to inject into requests
28+
*/
1229
public UserClient(Client delegate, String userToken) {
1330
this.delegate = delegate;
1431
this.userToken = userToken;
1532
}
1633

34+
/**
35+
* Creates a service proxy that automatically injects the user token into all requests.
36+
*
37+
* @param svcClass the service interface class
38+
* @param <TService> the service type
39+
* @return a proxy instance of the service with user token injection
40+
*/
1741
@Override
1842
public <TService> @NotNull TService create(Class<TService> svcClass) {
1943
return delegate.create(svcClass, userToken);
2044
}
2145

46+
/**
47+
* Returns the API key from the underlying client.
48+
*
49+
* @return the API key
50+
*/
2251
@Override
2352
public @NotNull String getApiKey() {
2453
return delegate.getApiKey();
2554
}
2655

56+
/**
57+
* Returns the API secret from the underlying client.
58+
*
59+
* @return the API secret
60+
*/
2761
@Override
2862
public @NotNull String getApiSecret() {
2963
return delegate.getApiSecret();
3064
}
3165

66+
/**
67+
* Sets the request timeout duration on the underlying client.
68+
*
69+
* @param timeoutDuration the timeout duration to set
70+
*/
3271
@Override
3372
public void setTimeout(@NotNull Duration timeoutDuration) {
3473
delegate.setTimeout(timeoutDuration);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
11
package io.getstream.chat.java.services.framework;
22

3+
/**
4+
* Factory interface for creating service instances with user-specific authentication.
5+
* <p>
6+
* Implementations of this interface are responsible for creating Retrofit service
7+
* proxies that inject the provided {@link UserToken} into API requests. This enables
8+
* per-user authentication without requiring separate HTTP client instances.
9+
* </p>
10+
* <p>
11+
* Package-private to control instantiation within the framework.
12+
* </p>
13+
*
14+
* @see UserToken
15+
* @see UserTokenCallRewriter
16+
*/
317
interface UserServiceFactory {
418

19+
/**
20+
* Creates a service instance that injects the specified user token into all requests.
21+
*
22+
* @param svcClass the service interface class to create
23+
* @param userToken the user token to inject into requests
24+
* @param <TService> the service interface type
25+
* @return a proxy instance of the service with token injection capabilities
26+
*/
527
<TService> TService create(Class<TService> svcClass, UserToken userToken);
628

729
}

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,6 @@
1212
* <p>
1313
* <b>Mechanism:</b> Uses Java reflection {@link java.lang.reflect.Proxy} to wrap the service
1414
* 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.
2415
*
2516
* @see UserTokenCallRewriter
2617
*/

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

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,9 @@
55

66
/**
77
* Smart user-aware service factory with automatic fallback mechanism.
8-
* <p>
8+
*
99
* 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.
10+
* and automatically falls back to {@link UserServiceFactoryTagging} if the proxy approach fails.
2211
*/
2312
final class UserServiceFactorySelector implements UserServiceFactory {
2413

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,11 @@
88
* <p>
99
* This implementation wraps the OkHttp call factory to automatically attach a {@link UserToken}
1010
* as a request tag. The token can then be retrieved by interceptors for authentication purposes.
11+
* </p>
1112
* <p>
1213
* <b>Mechanism:</b> Creates a new Retrofit instance with a custom call factory that tags
1314
* 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.
15+
* </p>
2216
*/
2317
final class UserServiceFactoryTagging implements UserServiceFactory {
2418

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
package io.getstream.chat.java.services.framework;
22

3+
/**
4+
* Immutable wrapper for a user authentication token.
5+
* <p>
6+
* This class encapsulates a user token string that is injected into HTTP requests
7+
* for per-user authentication in multi-tenant scenarios. The token is stored as a
8+
* request tag and retrieved by interceptors for adding authorization headers.
9+
* </p>
10+
* <p>
11+
* Package-private to prevent direct instantiation outside the framework.
12+
* </p>
13+
*/
314
final class UserToken {
415
private final String value;
516

17+
/**
18+
* Constructs a new UserToken with the specified value.
19+
*
20+
* @param value the token string value
21+
*/
622
UserToken(String value) {
723
this.value = value;
824
}
925

26+
/**
27+
* Returns the token string value.
28+
*
29+
* @return the token string
30+
*/
1031
String value() {
1132
return value;
1233
}

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

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,57 @@
99
import java.lang.reflect.Method;
1010

1111
/**
12-
* Dynamic proxy that intercepts Retrofit service calls and injects UserToken
13-
* into the request by modifying the internal rawCall field via reflection.
14-
*
15-
* This approach allows per-call authentication without creating multiple OkHttpClient
16-
* instances, making it suitable for multi-tenant systems with thousands of users.
12+
* Dynamic proxy that intercepts Retrofit service calls and injects {@link UserToken}
13+
* into requests for per-user authentication.
14+
* <p>
15+
* This class uses Java reflection to modify Retrofit's internal {@code Call} objects,
16+
* injecting a {@link UserToken} as a request tag. The token is then retrieved by
17+
* OkHttp interceptors to add authentication headers.
18+
* </p>
19+
*
20+
* @param <TService> the service interface type being proxied
21+
* @see UserToken
22+
* @see UserServiceFactory
1723
*/
1824
class UserTokenCallRewriter<TService> implements InvocationHandler {
25+
/**
26+
* Cached reference to Retrofit's internal rawCall field.
27+
* Uses double-checked locking for thread-safe lazy initialization.
28+
*/
1929
private static volatile Field rawCallField;
2030

2131
private final Call.Factory callFactory;
2232
private final TService delegate;
2333
private final UserToken token;
2434

35+
/**
36+
* Constructs a new call rewriter that injects the specified token.
37+
*
38+
* @param callFactory the OkHttp call factory for creating modified calls
39+
* @param delegate the original service implementation to proxy
40+
* @param token the user token to inject into requests
41+
*/
2542
UserTokenCallRewriter(@NotNull Call.Factory callFactory, @NotNull TService delegate, @NotNull UserToken token) {
2643
this.callFactory = callFactory;
2744
this.delegate = delegate;
2845
this.token = token;
2946
}
3047

48+
/**
49+
* Intercepts service method invocations to inject the user token.
50+
* <p>
51+
* This method ensures that all service methods return {@code retrofit2.Call<?>}
52+
* objects. If a method returns a different type, a {@link TokenInjectionException}
53+
* is thrown.
54+
* </p>
55+
*
56+
* @param proxy the proxy instance
57+
* @param method the method being invoked
58+
* @param args the method arguments
59+
* @return the modified Call with token injection
60+
* @throws Throwable if the underlying method throws an exception
61+
* @throws TokenInjectionException if the method doesn't return retrofit2.Call<?>
62+
*/
3163
@Override
3264
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
3365
Object result = method.invoke(delegate, args);
@@ -43,6 +75,17 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
4375
" did not return retrofit2.Call<?>. User token injection requires all service methods to return Call<?>.");
4476
}
4577

78+
/**
79+
* Injects the user token into a Retrofit call by modifying its internal OkHttp call.
80+
* <p>
81+
* The token is added as a request tag of type {@link UserToken}, which can be
82+
* retrieved by OkHttp interceptors for authentication purposes.
83+
* </p>
84+
*
85+
* @param originalCall the original Retrofit call
86+
* @return a cloned call with the user token injected
87+
* @throws TokenInjectionException if reflection fails or Retrofit's structure has changed
88+
*/
4689
private retrofit2.Call<?> injectTokenIntoCall(retrofit2.Call<?> originalCall) throws TokenInjectionException {
4790
retrofit2.Call<?> clonedCall = originalCall.clone();
4891

0 commit comments

Comments
 (0)