Webflux: Add Subscriber Context Values from In-Stream Objects - spring-webflux

I have a Webflux application and I'm using the subscriber context to populate the MDC values so that they'll propagate downstream. I've implemented the classes from this project to handle the MDC transfer between subscribers, setup a webfilter to add a request ID to incoming requests, and can see the request ID in the logs as part of the MDC.
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.contextWrite(Context.of("requestId", UUID.randomUUID().toString()));
}
Log output:
2021-09-30 17:15:29,963 [reactor-tcp-nio-2] INFO c.p.l.s.RepoService:33 - MDC[requestId=ec0b68cf-ba4d-4c7f-afa4-f67fc97ebcbf] - Found user
The request needs to pull a user record from the database and I need to add the user's ID and email to the context once the data has been fetched, but I can't figure out how to add values to the context from objects within the stream.
So here's what I'm trying to do:
public Mono<Response> doProcessing(String userId, Object object) {
return userRepo.findUserById(userId) //Returns UserEntity object
.flatMap(userEntity -> service.doMoreProcessing(userEntity, object))
.contextWrite(Context.of("userId", userId, "email", userEntity.getEmail()));
}
The problem is that I don't have access to the userEntity object when I call contextWrite(), so I can't add the user's email to the context using that call.
I've tried using Mono.deferredContextual()...
public Mono<Response> doProcessing(String userId, Object object) {
return userRepo.findUserById(userId) //Returns UserEntity object
.flatMap(userEntity -> Mono.deferContextual(ctx -> Mono.just(userEntity).contextWrite(Context.of(ctx).putAll(Context.of("userId", userId, "email", userEntity.getEmail()).readOnly()))))
.flatMap(userEntity -> service.doMoreProcessing(userEntity, object));
}
...and .transformDeferredContextual()...
public Mono<Response> doProcessing(String userId, Object object) {
return userRepo.findUserById(userId) //Returns UserEntity object
.transformDeferredContextual((userEntityMono, contextView) -> userEntityMono.flatMap(userEntity -> Mono.just(userEntity).contextWrite(Context.of(contextView).putAll(Context.of("userId", userId, "email", userEntity.getEmail()).readOnly()))))
.flatMap(userEntity -> service.doMoreProcessing(userEntity, object));
}
...but nothing has worked. How can I add data to the subscriber context from objects that are part of the process stream?

The context can store a mutable state object like a ConcurrentMap or better yet a #Data-annotated class that could be accessed and modified on the chain (iff, you can guarantee you're not modifying the same field in the chain).
To get the data you need to obtain it from the signal using doOnEach, as doOnNext does not provide the signal value where you can getContextView()
You can try something like this
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.contextWrite(Context.of("your-key", new ConcurrentHashMap<String, Object>()));
}
public Mono<Response> doProcessing(String userId, Object object) {
return userRepo.findUserById(userId)
.doOnEach(signal -> {
if (!signal.isOnNext()) return;
Optional<Map<String, Object>> optional = signal.getContextView().getOrEmpty("your-key");
optional.ifPresent(map -> {
map.put("userId", signal.get().getId());
map.put("email", signal.get().getEmail());
});
});
}

Related

WebFlux & formation DTO

Hello recently started studying Webflux.
And sometimes I encounter the tasks that you need to form a simple DTO and return it
Take for example the usual class dto
#Data
#Builder
public static class Dto {
private long id;
private String code1;
private String code2;
}
And a primitive service with two methods...
#Nullable Mono<String> getCode1(long id);
#Nullable String getCode2(long id);
And wrote a method that forms at the output of Mono
private Mono<Dto> fill(long id) {
var dto = Dto.builder()
.id(id)
.build();
//doOnNext
var dtoMono1 = service.getCode1(id)
.map(code -> {
dto.setCode1(code);
return dto;
})
.doOnNext(innerDto -> innerDto.setCode2(service.getCode2(id)));
//map
var dtoMono2 = service.getCode1(id)
.map(code -> {
dto.setCode1(code);
return dto;
})
.map(unused -> service.getCode2(id))
.map(code -> {
dto.setCode1(code);
return dto;
});
//just
var dtoMono3 = Mono.just(dto)
.flatMap(innerDto -> service.getCode1(innerDto.getId()));
//just
var dtoMono4 = Mono.fromCallable(() -> dto)
.subscribeOn(Schedulers.boundedElastic())
.flatMap(innerDto -> service.getCode1(innerDto.getId()));
}
QUESTION:
Is it possible to simply create DTO and use it in the Webflux call
chain ... Or I need to wrap it in mono.just or mono.fromcallable
(what are the pros and cons)
How best to fill in values via doOnNext
or through MAP. An extra line (Return DTO) appears in the case of
MAP and some people also told me if for example NULL comes, doOnNext will
miss it and go further to fill up current dto. But on the other, the MAP is used
to transform the object, and the doOnNext is more for debugging and
logging
Thanks you...
How about using zip operator in such a case?
I hope this example can help you:
private Mono<Dto> fill(long id) {
return Mono.zip(someService.getCode1(id), Mono.just(someService.getCode2(id)))
.map(tuple ->
Dto.builder()
.id(id)
.code1(tuple.getT1())
.code2(tuple.getT2())
.build()
);
}

Why documentt.data.getValue() gives empty string? [duplicate]

A custom object that takes a parameter of (DocumentSnapShot documentsnapShot). also is an inner object from Firebase that retrieves a snapshot and set the values to my custom model also have its argument (DocumentSnapShot documentsnapShot). However, I wish to get the data from Firebase and pass it to my custom argument because mine takes multiple data not only Firebase. And it's not possible to iterate Firestore without an override.
Here's the code:
public UserSettings getUserSettings(DocumentSnapshot documentSnapshot){
Log.d(TAG, "getUserSettings: retrieving user account settings from firestore");
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
settings.setDisplay_name(documentSnapshot.getString("display_name"));
settings.setUsername(documentSnapshot.getString("username"));
settings.setWebsite(documentSnapshot.getString("website"));
settings.setProfile_photo(documentSnapshot.getString("profile_photo"));
settings.setPosts(documentSnapshot.getLong("posts"));
settings.setFollowers(documentSnapshot.getLong("followers"));
settings.setFollowing(documentSnapshot.getLong("following"));
}
});
}
You cannot return something now that hasn't been loaded yet. Firestore loads data asynchronously, since it may take some time for this. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available. If you want to pass settings object to another method, just call that method inside onSuccess() method and pass that object as an argument. So a quick fix would be this:
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
yourMethod(settings);
}
One more thing to mention is that you don't need to set the those values to object that already have them. You are already getting the data from the database as an object.
So remember, onSuccess() method has an asynchronous behaviour, which means that is called even before you are getting the data from your database. If you want to use the settings object outside that method, you need to create your own callback. To achieve this, first you need to create an interface like this:
public interface MyCallback {
void onCallback(UserAccountSettings settings);
}
Then you need to create a method that is actually getting the data from the database. This method should look like this:
public void readData(MyCallback myCallback) {
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
myCallback.onCallback(settings);
}
});
}
In the end just simply call readData() method and pass an instance of the MyCallback interface as an argument wherever you need it like this:
readData(new MyCallback() {
#Override
public void onCallback(UserAccountSettings settings) {
Log.d("TAG", settings.getDisplay_name());
}
});
This is the only way in which you can use that object of UserAccountSettings class outside onSuccess() method. For more informations, you can take also a look at this video.
Use LiveData as return type and observe the changes of it's value to execute desired operation.
private MutableLiveData<UserAccountSettings> userSettingsMutableLiveData = new MutableLiveData<>();
public MutableLiveData<UserAccountSettings> getUserSettings(DocumentSnapshot documentSnapshot){
DocumentReference mSettings = mFirebaseFirestore.collection("user_account_settings").document(userID);
mSettings.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
UserAccountSettings settings = documentSnapshot.toObject(UserAccountSettings.class);
settings.setDisplay_name(documentSnapshot.getString("display_name"));
settings.setUsername(documentSnapshot.getString("username"));
settings.setWebsite(documentSnapshot.getString("website"));
settings.setProfile_photo(documentSnapshot.getString("profile_photo"));
settings.setPosts(documentSnapshot.getLong("posts"));
settings.setFollowers(documentSnapshot.getLong("followers"));
settings.setFollowing(documentSnapshot.getLong("following"));
userSettingsMutableLiveData.setValue(settings);
}
});
return userSettingsMutableLiveData;
}
Then from your Activity/Fragment observe the LiveData and inside onChanged do your desired operation.
getUserSettings().observe(this, new Observer<UserAccountSettings>() {
#Override
public void onChanged(UserAccountSettings userAccountSettings) {
//here, do whatever you want on `userAccountSettings`
}
});

spring webflux, convert Flux to arraylist (or iterable list) [duplicate]

I have a repository that returns a flux and wanted to set the result to another object which is expecting a list. Is there any other way to get the results as a list without blocking?
The block is working but it is taking long time.
public class FluxToListTest {
#Autowired PostRepository postRepository;
public void setUserPosts(User user) {
user.setPostList(postRepository.findAllByOrderId(user.getId()).collectList().block());
}
}
interface PostRepository {
Flux<Post> findAllByOrderId(final UUID userId);
}
#Data
class User {
UUID id;
List<Post> postList;
}
class Post {
UUID id;
String content;
}
In short - NO.
You don't need to extract List from Flux.
If you've started use Reactor Streams - stay with it.
Try this code:
public void setUserPosts(User user) {
postRepository.findAllByOrderId(user.getId())
.collectList()
.doOnNext(user::setPostList)// (1)
.subscribe(); // (2)
}
if you set operation is blocking please use publishOn/subscribeOn to avoid blocking for the all stream.
it starts your stream performing

I am trying to implement multiGet operation in Spring on Redis, it throws me an error

I am trying to execute multiGet function in Spring on Redis. It throws me an error. I have implemented get function successfully but while implementing multiGet it asks me for a Collection as second parameter. I am not sure what to enter? Can someone please guide me here.
Here is my code for multiGet()
Method definition:
#Override
public User findById_MultiGet(String id) {
return (User)hashOperations.multiGet("USER", id);
}
Code In Controller :
#GetMapping("Map/MultiGet/{id}")
public User allMultiGet(#PathVariable("id") final String id) {
// MultiGet function
return userRepository.findById_MultiGet(id);
}
Error for above multiget method is multiget(Object, Collection) type not (String,String) type
Below code for Get function is working.
public User findById(String id) {
return (User)hashOperations.get("USER", id);
}
Code In Controller for Get function :
#GetMapping("Map/Get/{id}")
public User allGet(#PathVariable("id") final String id) {
// Get function
return userRepository.findById(id);
}
For multiGet the second parameter should be a Collection like a List (in case you want the values of the list returned as result on the same positions as their belonging keys in the input list) or a Set.
In your example this would be something like this:
List<Object> values = hashOperations.multiGet("USER", Arrays.asList("id", "name"));
Object id = values.get(0);
Object name = values.get(1);

Wrong Thread.CurrentPrincipal in async WCF end-method

I have a WCF service which has its Thread.CurrentPrincipal set in the ServiceConfiguration.ClaimsAuthorizationManager.
When I implement the service asynchronously like this:
public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
{
// Audit log call (uses Thread.CurrentPrincipal)
var task = Task<int>.Factory.StartNew(this.WorkerFunction, state);
return task.ContinueWith(res => callback(task));
}
public string EndMethod1(IAsyncResult ar)
{
// Audit log result (uses Thread.CurrentPrincipal)
return ar.AsyncState as string;
}
private int WorkerFunction(object state)
{
// perform work
}
I find that the Thread.CurrentPrincipal is set to the correct ClaimsPrincipal in the Begin-method and also in the WorkerFunction, but in the End-method it's set to a GenericPrincipal.
I know I can enable ASP.NET compatibility for the service and use HttpContext.Current.User which has the correct principal in all methods, but I'd rather not do this.
Is there a way to force the Thread.CurrentPrincipal to the correct ClaimsPrincipal without turning on ASP.NET compatibility?
Starting with a summary of WCF extension points, you'll see the one that is expressly designed to solve your problem. It is called a CallContextInitializer. Take a look at this article which gives CallContextInitializer sample code.
If you make an ICallContextInitializer extension, you will be given control over both the BeginXXX thread context AND the EndXXX thread context. You are saying that the ClaimsAuthorizationManager has correctly established the user principal in your BeginXXX(...) method. In that case, you then make for yourself a custom ICallContextInitializer which either assigns or records the CurrentPrincipal, depending on whether it is handling your BeginXXX() or your EndXXX(). Something like:
public object BeforeInvoke(System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.IClientChannel channel, System.ServiceModel.Channels.Message request){
object principal = null;
if (request.Properties.TryGetValue("userPrincipal", out principal))
{
//If we got here, it means we're about to call the EndXXX(...) method.
Thread.CurrentPrincipal = (IPrincipal)principal;
}
else
{
//If we got here, it means we're about to call the BeginXXX(...) method.
request.Properties["userPrincipal"] = Thread.CurrentPrincipal;
}
...
}
To clarify further, consider two cases. Suppose you implemented both an ICallContextInitializer and an IParameterInspector. Suppose that these hooks are expected to execute with a synchronous WCF service and with an async WCF service (which is your special case).
Below are the sequence of events and the explanation of what is happening:
Synchronous Case
ICallContextInitializer.BeforeInvoke();
IParemeterInspector.BeforeCall();
//...service executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();
Nothing surprising in the above code. But now look below at what happens with asynchronous service operations...
Asynchronous Case
ICallContextInitializer.BeforeInvoke(); //TryGetValue() fails, so this records the UserPrincipal.
IParameterInspector.BeforeCall();
//...Your BeginXXX() routine now executes...
ICallContextInitializer.AfterInvoke();
//...Now your Task async code executes (or finishes executing)...
ICallContextInitializercut.BeforeInvoke(); //TryGetValue succeeds, so this assigns the UserPrincipal.
//...Your EndXXX() routine now executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();
As you can see, the CallContextInitializer ensures you have opportunity to initialize values such as your CurrentPrincipal just before the EndXXX() routine runs. It therefore doesn't matter that the EndXXX() routine assuredly is executing on a different thread than did the BeginXXX() routine. And yes, the System.ServiceModel.Channels.Message object which is storing your user principal between Begin/End methods, is preserved and properly transmitted by WCF even though the thread changed.
Overall, this approach allows your EndXXX(IAsyncresult) to execute with the correct IPrincipal, without having to explicitly re-establish the CurrentPrincipal in the EndXXX() routine. And as with any WCF behavior, you can decide if this applies to individual operations, all operations on a contract, or all operations on an endpoint.
Not really the answer to my question, but an alternate approach of implementing the WCF service (in .NET 4.5) that does not exhibit the same issues with Thread.CurrentPrincipal.
public async Task<string> Method1()
{
// Audit log call (uses Thread.CurrentPrincipal)
try
{
return await Task.Factory.StartNew(() => this.WorkerFunction());
}
finally
{
// Audit log result (uses Thread.CurrentPrincipal)
}
}
private string WorkerFunction()
{
// perform work
return string.Empty;
}
The valid approach to this is to create an extension:
public class SLOperationContext : IExtension<OperationContext>
{
private readonly IDictionary<string, object> items;
private static ReaderWriterLockSlim _instanceLock = new ReaderWriterLockSlim();
private SLOperationContext()
{
items = new Dictionary<string, object>();
}
public IDictionary<string, object> Items
{
get { return items; }
}
public static SLOperationContext Current
{
get
{
SLOperationContext context = OperationContext.Current.Extensions.Find<SLOperationContext>();
if (context == null)
{
_instanceLock.EnterWriteLock();
context = new SLOperationContext();
OperationContext.Current.Extensions.Add(context);
_instanceLock.ExitWriteLock();
}
return context;
}
}
public void Attach(OperationContext owner) { }
public void Detach(OperationContext owner) { }
}
Now this extension is used as a container for objects that you want to persist between thread switching as OperationContext.Current will remain the same.
Now you can use this in BeginMethod1 to save current user:
SLOperationContext.Current.Items["Principal"] = OperationContext.Current.ClaimsPrincipal;
And then in EndMethod1 you can get the user by typing:
ClaimsPrincipal principal = SLOperationContext.Current.Items["Principal"];
EDIT (Another approach):
public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
{
var task = Task.Factory.StartNew(this.WorkerFunction, state);
var ec = ExecutionContext.Capture();
return task.ContinueWith(res =>
ExecutionContext.Run(ec, (_) => callback(task), null));
}