I have read similar posts on this, but is this a right way to use computeIfAbsent function? cookieMap is a HashMap and responses is an Object which contains all the headers, cookies, responses, status Code etc...
cookieMap.computeIfAbsent("Varlink", varLink -> {
if (responses.getCookie("VARLINK").length() < 1) {
throw new ProviderException("Varlink not present in response, check response status!!!");
}
return responses.getCookie("VARLINK");
});
I will need to add multiple keys like this to the cookieMap. My initial thought was to put everything inside an If condition, but due to certain restrictions we are not supposed to have nested if-else conditions (I guess the Code Reviewer took the book Clean Code too seriously)
If responses and cookieMap are two different sources of data, then your snippet is correct. The only concern is calling cookieMap::getCookie twice which might be resolved using a variable as someone has suggested in the comments.
I'd shorten the entire expression using Optional to:
cookieMap.computeIfAbsent("Varlink", v -> {
Optional.of(respones.getCookie("VARLINK")) // Gets a cookie
.filter(c -> c.length() >= 1) // Filters the length
.orElseThrow(() -> new ProviderException("...")); // Returns only if present
});
Related
Let's say that I have a method addVoteToSong like:
public Mono<Map<Song, VoteKind>> addVoteToSong(Principal principal, String songId, VoteKind voteKind) {
return
userRepository.findUserByUsername(principal.getName())
.doOnSuccess(song -> songRepository.findSongById(songId))
.doOnSuccess(vote -> voteRepository.add(Vote.builder().song()))
.//(the rest of the code)
}
I want to pass a result from the line:
userRepository.findUserByUsername(principal.getName())
and
.doOnSuccess(song -> songRepository.findSongById(songId))
to the built object in the line:
.doOnSuccess(vote -> voteRepository.add(Vote.builder().song(here result from findSongById).user(here result from findUserByUsername))
Here comes the question, is it possible to reuse previous API call result in the next doOnSuccess method or I should split find API calls at the same time, giving up on Reactor's cascading operations? On the internet, I have found examples with single save method without basing on the indirect result of the reactive stream and that's why question occurred. I will be grateful for suggestions on how to reach a goal.
Martin,
First of all, be aware that .doOnXXX are just callbacks that will be executed on some archived conditions. You should avoid putting a business logic inside of them.
Coming back to the question, the first idea that comes to my mind is to benefit from zip operator. So you have to put 2 publishers .findUserByUsername and .findSongById and combine the result using BiFunction. So you can try the following:
public Mono<Map<Song, VoteKind>> addVoteToSong(Principal principal, String songId, VoteKind voteKind) {
return Mono
.zip(
userRepository.findUserByUsername(principal.getName()),
songRepository.findSongById(songId),
(user, song) -> voteRepository.add(Vote.builder().song(song).user(user).build())
)
.flatMap(Function.identity())
// your code is here
}
I have this code which I want to refactor using a functional style, using Java 8. I would like to remove the mutable object currentRequest and still return the filtered request.
HttpRequest currentRequest = httpRequest;
for (Filter filter : filters) {
currentRequest = filter.doFilter(currentRequest);
}
The aim is to pass a request to the filter.doFilter method, and take the output and pass it back into the filter.doFilter method, and continue to do this until all filters are applied.
For example in a more convoluted way to the for loop
HttpRequest filteredRequest1 = filters.get(0).doFilter(currentRequest);
HttpRequest filteredRequest2 = filters.get(1).doFilter(filteredRequest1);
HttpRequest filteredRequest3 = filters.get(2).doFilter(filteredRequest2);
...
I think this is a case for composing functions, and the doFilter method should be a function like below:
Function<HttpRequest, HttpRequest> applyFilter = request -> filters.get(0).doFilter(request);
But I know this is totally wrong, as I got stuck here.
The other way I was thinking was to use reduce, but I cannot see a way of using it in this case.
If you could help me out with a way of doing this, or point me to some resource that will be great.
It looks like you may want to do a reduce with your HttpRequest as its identity. Each step of the reduce will combine the intermediate result with the next filter, like so:
filters.stream().reduce(currentRequest,
(req, filter) -> filter.doFilter(req),
(req1, req2) -> throwAnExceptionAsWeShouldntBeHere());
Note: the last function is used to merge two HttpRequests together if a parallel stream is used. If that's the route you wish to go down, then proceed with caution.
Here's a way that streams the filters and maps each one of them to a UnaryOperator<HttpRequest>. Then, all the functions are reduced via the Function.andThen operator and finally, if the filters collections wasn't empty, the resulting composed function is executed with the currentRequest as an argument:
HttpRequest result = filters.stream()
.map(filter -> ((Function<HttpRequest, HttpRequest>) filter::doFilter))
.reduce(Function::andThen)
.map(function -> function.apply(currentRequest))
.orElse(currentRequest);
Consider this example:
I have a file downloading in sequence. If one download fails, it should move to next.
Psudo code:
Observable.from(urls)
.concatMap(url -> downloadObservable(url))
There is no option for moving to next url if the download fails.
There is no way to skip with onErrorResumeNext() as I just want to move to next url. Can anyone help?
There is an operator for this: concatMapDelayError since 1.3. In general, if there is a reason errors could be delayed until all sources have been consumed fully, there is likely a opNameDelayError operator for it.
Observable.from(urls)
.concatMapDelayError(url -> downloadObservable(url))
.doOnError(error -> {
if (error instanceof CompositeException) {
System.out.println(((CompositeException)error).getExceptions().size());
} else {
System.out.println(1);
}
});
(The doOnError addendum comes from the updated OP's cross post on the RxJava issue list.)
If you are using RxJava 1, a quick and dirty solution is to return null when the download fails and then filter them out:
Observable
.from(urls)
.concatMap(url -> downloadObservable(url).onErrorReturn(null))
.filter(result -> result != null)
A nicer solution would be to create a wrapper for the result having a method like wasSuccessful() for checking in the filter and a method like getResult() for extracting the result from the wrapper. This way you don't have to handle nulls.
According to: https://github.com/ReactiveX/RxJava/issues/3870 there is no way to do this. Of course you can introduce some other error handling, i.e. handle error inside downloadObservable then filter null answers.
You have to think that is a pipeline so, in case you don't want to stop the emission of the pipeline, you have to control the error and return something in order to continue with the next emission.
The only way to use onErrorResumeNext and not stop the emission after that, is if it´s executed in a flatMap
Observable.from(urls)
.flatMap(url -> downloadObservable(url)
.onErrorResumeNext(t -> Observable.just("Something went wrong"))))
You can see an example here https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java
The logic here is that if the ratings in the database are empty, then I want to get them from the API. I have the following code:
Observable.from(settingsRatingRepository.getRatingsFromDB())
.toList()
.switchIfEmpty(settingsRatingRepository.getSettingsRatingModulesFromAPI())
.compose(schedulerProvider.getSchedulers())
.subscribe(ratingsList -> {
view.loadRatingLevels(ratingsList, hideLocks);
}, this::handleError);
The getRatingsFromDB() call returns List<SettingRating>, but the API call returns Observable<List<SettingRating>>.
However, when I unit test this, when I pass an empty list from the database call, it does not execute the API call. Can someone pls help me in this matter. This is my unit test code:
when(mockSettingsRatingsRepository.getRatingsFromDB()).thenReturn(Collections.emptyList());
List<SettingsRating> settingsRatings = MockContentHelper.letRepositoryReturnSettingsRatingsFromApi(mockSettingsRatingsRepository);
settingsParentalPresenter.onViewLoad(false);
verify(mockView).loadRatingLevels(settingsRatings, false);
As #Kiskae mentioned, it's the fact that I am confusing an empty list with an empty Observable. Therefore, I have used the following which is what I want:
public void onViewLoad(boolean hideLocks) {
Observable.just(settingsRatingRepository.getRatingsFromDB())
.flatMap(settingsRatings -> {
if (settingsRatings.isEmpty()) {
return settingsRatingRepository.getSettingsRatingModules();
} else {
return Observable.just(settingsRatings);
}
})
.compose(schedulerProvider.getSchedulers())
.subscribe(ratingsList -> {
view.loadRatingLevels(ratingsList, hideLocks);
}, this::handleError);
}
Observable#toList() returns a single element. If the observable from which it gets its elements is empty, it will emit an empty list. So by definition the observable will never be empty after calling toList().
switchIfEmpty will only be called when your observer completes without emitting any items.
Since you are doing toList it will emit list object. Thats why your switchIfEmpty is never getting called.
If you want to get data from cache and fallback to your api if cache is empty, use concat along with first or takeFirst operator.
For example:
Observable.concat(getDataFromCache(), getDataFromApi())
.first(dataList -> !dataList.isEmpty());
Building on answer by #kiskae, your use of a toList() emits the elements aggregated as a single List.
There is an alternative to the use of Observable.just() + a flatMap here.
Observable.from will iterate over the list returned by your service and emit each individual items, so an Observable<Rating>. If said list is empty, it naturally produces an empty Observable. Your API call also produces an Observable<Rating>, and in both cases you want to reaggregate that back into a List<Rating> for further processing.
So just move the toList() from your original code down one line, after the switchIfEmpty calling the API:
Observable.from(settingsRatingRepository.getRatingsFromDB())
.switchIfEmpty(settingsRatingRepository.getSettingsRatingModulesFromAPI())
.toList()
.compose(schedulerProvider.getSchedulers())
.subscribe(ratingsList -> {
view.loadRatingLevels(ratingsList, hideLocks);
}, this::handleError);
Granted, that solution may produce a bit more garbage (as the db's List is turned into an Observable just to be turned into a new List later on).
JavaRDD<Text> tx= counts2.map(new Function<Object, Text>() {
#Override
public Text call(Object o) throws Exception {
// TODO Auto-generated method stub
if (o.getClass() == Dict.class) {
Dict rkd = (Dict) o;
return new Text(rkd.getId());
} else {
return null ;
}
}
});
tx.saveAsTextFile("/rowkey/Rowkey_new");
I am new to Spark, I want to save this file, but I got the Null exception. I don't want to use return new Text() to replace return null,because it will insert a blank line to my file. So how can I solve this problem?
Instead of putting an if condition in your map, you simply use that if condition to build a RDD filter. The Spark Quick Start is a good place to start. There is also a nice overview of other transformations and actions.
Basically your code can look as follows (if you are using Java 8):
counts2
.filter((o)->o instanceof Dict)
.map(o->new Text(((Dict)o).getId()))
.saveAsTextFile("/rowkey/Rowkey_new");
You had the intention to map one incoming record to either zero or one outgoing record. This cannot be done with a map. However, filter maps to zero or one records with incoming record matches outgoing record, and flatMap gives you some more flexibility by allowing to map to zero or more outgoing records of any type.
It is strange, but not inconceivable, you create non-Dict objects that are going to be filters out further downstream anyhow. Possibly you can consider to push your filter even further upstream to make sure you only create Dict instances. Without knowing the rest of your code, this is only a assumption of course, and is not part of your original question anyhow.