Throw exception by condition. Spring web flux - reactive-programming

I have the following reactive code. And I want to throw my custom exception, if return value is null
ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(authentication ->(UserAuthenticationToken) authentication)
.map(UserAuthenticationToken::getUserPrincipalName)
//Here I want to throw exception, if
//UserAuthenticationToken::getUserPrincipalName will return null
something like :
.map(UserAuthenticationToken::getUserPrincipalName)
.switchIfEmpty(Mono.error(new MissingPrincipalException("Missing email field in the JWT token")));
or :
.map(UserAuthenticationToken::getUserPrincipalName)
.filter(principal -> !Objects.isNull(principal))
.switchIfEmpty(Mono.error(new MissingPrincipalException("Missing email field in the JWT token")))
does not work.

The reactive streams specification disallows null values in a sequence, therefore you can't use map(UserAuthenticationToken::getUserPrincipalName).
One way to handle such case is to use flatMap
ReactiveSecurityContextHolder.getContext()
.map(ctx -> (UserAuthenticationToken) ctx.getAuthentication())
.flatMap(token -> {
if (token.getUserPrincipalName() == null) {
return Mono.error(new MissingPrincipalException("Missing email field in the JWT token"));
}
return Mono.just(token.getUserPrincipalName());
})
You can also use handle operator to remove any nulls. Reactor Reference Guide has a good example for Using handle for a "map and eliminate nulls" scenario

Related

WebFlux/Reactor: checking conditions before+after Flux execution with doOnComplete

I'm already querying some external resource with Flux.using(). Now I want to implement a kind of optimistic locking: read some state before query starts to execute and check if it was updated after query is finished. If so - throw some exception to break http request handling.
I've achieved this by using doOnComplete:
final AtomicReference<String> initialState = new AtomicReference<>();
return Flux.just("some", "constant", "data")
.doOnComplete(() -> initialState.set(getState()))
.concatWith(Flux.using(...)) //actual data query
.doOnComplete(() -> {if (!initialState.get().equals(getState())) throw new RuntimeException();})
.concatWithValues("another", "constant", "data")
My questions:
Is it correct? Is it guaranteed that 1st doOnComplete lambda would be finished before Flux.using() and is it guaranteed that 2nd doOnComplete lambda would be executed strictly after?
Does more elegant solution exists?
The first doOnComplete would be executed after Flux.just("some", "constant", "data") emits all elements and the second one after emitted Publisher defined in concatWith completes successfully. This is working because both publishers have a finite number of elements.
With the proposed approach, however the pre-/postconditions from a particular operation are handled outside of the operations at a higher level. In other words, the condition check belonging to the operation is leaking to the flux definition.
Suggestion, pushing the condition check down to the operation:
var otherElements = Flux.using( // actual data query
() -> "other",
x -> {
var initialState = getState();
return Flux.just(x).doOnComplete(() ->
{ if (!initialState.equals(getState())) throw new IllegalStateException(); }
);
},
x -> { }
);
Flux.just("some", "constant", "data")
.concatWith(otherElements)
.concatWith(Mono.just("another")) // "constant", "data" ...

How to stop sending to kafka topic when control goes to catch block Functional kafka spring

could you please advise , how can I stop sending to my 3rd kafka topic, when the control reaches the catch block, currently the message is sent to both error topic as well as the topic to which it should send in case of normal processing. A snippet of code is as below:
#Component
public class Abc {
private final StreamBridge streamBridge;
public Abc (StreamBridge streamBridge)
this.streamBridge = streamBridge;
#Bean
public Function<KStream<String, KafkaClass>, KStream<String,KafkaClass>> hiProcess() {
return input -> input.map((key,value) -> {
try{
KafkaClass stream = processFunction();
}
catch(Exception e) {
Message<KakfaClass> mess = MessageBuilder.withPayload(value).build();
streamBridge.send("errProcess-out-0". mess);
}
return new KeyValue<>(key, stream);
})
}
}
This can be implemented using the following pattern:
KafkaClass stream;
return input -> input
.branch((k, v) -> {
try {
stream = processFunction();
return true;
}
catch (Exception e) {
Message<KakfaClass> mess = MessageBuilder.withPayload(value).build();
streamBridge.send("errProcess-out-0". mess);
return false;
}
},
(k, v) -> true)[0]
.map((k, v) -> new KeyValue<>(k, stream));
Here, we are using the branching feature (API) of KStream to split your input into two paths - normal flow and the one causing the errors. This is accomplished by providing two filters to the branch method call. The first filter is the normal flow in which you call the processFunction method and get a response back. If we don't get an exception, the filter returns true, and the result of the branch operation is captured in the first element of the output array [0] which is processed downstream in the map operation in which it sends the final result to the outbound topic.
On the other hand, if it throws an exception, it sends whatever is necessary to the error topic using StreamBridge and the filter returns false. Since the downstream map operation is only performed on the first element of the array from branching [0], nothing will be sent outbound. When the first filter returns false, it goes to the second filter which always returns true. This is a no-op filter where the results are completely ignored.
One downside of this particular implementation is that you need to store the response from processFunction in an instance field and then mutate on each incoming KStream record so that you can access its value in the final map method where you send the output. However, for this particular use case, this may not be an issue.

When calling findAndRemove, what are the happy and error return types?

I want to understand what calling .findAndRemove(document("userId" -> userId)) will return when:
document exists
document doesn't exist
Code:
userColl.findAndRemove(document("userId" => userId)).map(r => ...)
I want to return either a success, or a failure with the reason it failed.
I know there are the following values returned:
r.value
r.lastError
r.result[A]
I'm not sure how to return the correct value after calling findAndRemove and mapping on it.

RxJava2 Single take different route based on the item

I have the following code:
Single<Response<User>> single = service.registerUser();
single
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.map(Response::body)
.flatMap(parentsRepsitory::writeUser)
.observeOn(AndroidSchedulers.mainThread())
.flatMap(parentsRepsitory::getUser)
Where the parentsRepository is a repo wraping my realm database. The problems come when the server returns validation errors, however. So somewhere in my stream i want to have the equivalent of
if(response.code() == 201){
// CONTINUE STREAM USING THE LOGIC THAT HANDLES SUCCESS
}elseif(response.code() == 400){
// CONTINUE STREAM USING LOGIC TO HANDLE THE VALIDATION ERRORS
}
A solution I have previously implemented is as follows:
Observable<Response<User>> observable_from_api =
service.attemptLogin(username, password)
.share();
observable_from_api
.filter(response -> response.code() == HttpStatus.HTTP_STATUS_200_OK)
.//handle logic for success
observable_from_api
.filter(response -> response.code() == HttpStatus.HTTP_STATUS_400_BAD_REQUEST)
.//handle logic for validation errors
I don't like this solution for several different reasons. The main one being it just does not seem right. The second one being that the .share() method is only available on an Observable object. Since my network operation emits only one responce I would much rather use Single instead, but the .share() method is not available there.
Excuse me if this is a duplicate question, I have done some digging around and only found the solution I mentioned. I want to either see the optimal solution or be told explicitly that this is in fact the optimal solution.
I think you need to define which kind of data you want your consumer to receive. I assume you want to receive in the consumer a User object.
These are the signatures of the method that you should create:
Single<User> handleSuccess(Response<User> response)
Single<User> handleError(Response<User> response)
And then you create you stream in this way:
service.registerUser()
.flatMap(response -> {
if (response.success) {
return handleSuccess(response);
} else {
return handleError(response);
}
})
.subscribe(user -> logd("user: " + user.name));

ReactiveMongo: How to deal with database errors when inserting new documents

I've a MongoDB collection where I store User documents like this:
{
"_id" : ObjectId("52d14842ed0000ed0017cceb"),
"email": "joe#gmail.com",
"firstName": "Joe"
...
}
Users must be unique by email address, so I added an index for the email field:
collection.indexesManager.ensure(
Index(List("email" -> IndexType.Ascending), unique = true)
)
And here is how I insert a new document:
def insert(user: User): Future[User] = {
val json = user.asJson.transform(generateId andThen copyKey(publicIdPath, privateIdPath) andThen publicIdPath.json.prune).get
collection.insert(json).map { lastError =>
User(json.transform(copyKey(privateIdPath, publicIdPath) andThen privateIdPath.json.prune).get).get
}.recover {
throw new IllegalArgumentException(s"an user with email ${user.email} already exists")
}
}
In case of error, the code above throws an IllegalArgumentException and the caller is able to handle it accordingly. BUT if I modify the recover section like this...
def insert(user: User): Future[User] = {
val json = user.asJson.transform(generateId andThen copyKey(publicIdPath, privateIdPath) andThen publicIdPath.json.prune).get
collection.insert(json).map { lastError =>
User(json.transform(copyKey(privateIdPath, publicIdPath) andThen privateIdPath.json.prune).get).get
}.recover {
case e: Throwable => throw new IllegalArgumentException(s"an user with email ${user.email} already exists")
}
}
... I no longer get an IllegalArgumentException, but I get something like this:
play.api.Application$$anon$1: Execution exception[[IllegalArgumentException: DatabaseException['E11000 duplicate key error index: gokillo.users.$email_1 dup key: { : "giuseppe.greco#agamura.com" }' (code = 11000)]]]
... and the caller is no longer able to handle the exception as it should. Now the real questions are:
How do I handle the diverse error types (i.e. the ones provided by LastError) in the recover section?
How do I ensure the caller gets the expected exceptions (e.g. IllegalArgumentException)?
Finally I was able to manage things correctly. Here below is how to insert an user and handle possible exceptions with ReactiveMongo:
val idPath = __ \ 'id
val oidPath = __ \ '_id
/**
* Generates a BSON object id.
*/
protected val generateId = __.json.update(
(oidPath \ '$oid).json.put(JsString(BSONObjectID.generate.stringify))
)
/**
* Converts the current JSON into an internal representation to be used
* to interact with Mongo.
*/
protected val toInternal = (__.json.update((oidPath \ '$oid).json.copyFrom(idPath.json.pick))
andThen idPath.json.prune
)
/**
* Converts the current JSON into an external representation to be used
* to interact with the rest of the world.
*/
protected val toExternal = (__.json.update(idPath.json.copyFrom((oidPath \ '$oid).json.pick))
andThen oidPath.json.prune
)
...
def insert(user: User): Future[User] = {
val json = user.asJson.transform(idPath.json.prune andThen generateId).get
collection.insert(json).transform(
success => User(json.transform(toExternal).get).get,
failure => DaoServiceException(failure.getMessage)
)
}
The user parameter is a POJO-like instance with an internal representation in JSON – User instances always contain valid JSON since it is generated and validated in the constructor and I no longer need to check whether user.asJson.transform fails.
The first transform ensures there is no id already in the user and then generates a brand new Mongo ObjectID. Then, the new object is inserted in the database, and finally the result converted back to the external representation (i.e. _id => id). In case of failure, I just create a custom exception with the current error message. I hope that helps.
My experience is more with the pure java driver, so I can only comment on your strategy for working with mongo in general -
It seems to me that all you're accomplishing by doing the query beforehand is duplicating mongos uniqueness check. Even with that, you still have to percolate an exception upwards because of possible failure. Not only is this slower, but it's vulnerable to a race condition because the combination of your query + insert is not atomic. In that case you'd have
request 1: try to insert. email exists? false - Proceed with insert
request 2: try to insert. email exists? false - Proceed with insert
request 1: succeed
request 2: mongo will throw the database exception.
Wouldn't it be simpler to just let mongo throw the db exception and throw your own illegal argument if that happens?
Also, pretty sure the id will be generated for you if you omit it, and that there's a simpler query for doing your uniqueness check, if that's still the way you want to code it. At least in the java driver you can just do
collection.findOne(new BasicDBObject("email",someemailAddress))
Take a look at upsert mode of the update method (section "Insert a New Document if No Match Exists (Upsert)"): http://docs.mongodb.org/manual/reference/method/db.collection.update/#insert-a-new-document-if-no-match-exists-upsert
I asked a similar question a while back on reactivemongo's google group. You can have another case inside the recovery block to match a LastError object, query its error code, and handle the error appropriately. Here's the original question:
https://groups.google.com/forum/#!searchin/reactivemongo/alvaro$20naranjo/reactivemongo/FYUm9x8AMVo/LKyK01e9VEMJ