We obtain a list of data from a SQL Server database. I want to return the list when there is data, but when there is not, I want to return a "No Content" status. My code:
public class Main {
public static void main(String[] args) {
var main = new Main();
var result = main.controllerMethod();
System.out.println("Result: " + result.blockingGet());
}
public Flowable<Person> personList(){
List<Person> repositoryList = List.of();
return repositoryList
.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
if(list.isEmpty()) return Flowable.empty();
else return Flowable.fromIterable(list);
}));
}
public Maybe<ResponseEntity<Flowable<Person>>> controllerMethod(){
var httpStatus =
new AtomicReference<>(HttpStatus.OK);
return Maybe.just(personList()
.switchIfEmpty(subs -> Completable.fromAction(() ->
httpStatus.set(HttpStatus.NO_CONTENT)).toFlowable()))
.map(person -> ResponseEntity.status(httpStatus.get())
.body(person));
}
}
result:
Result: <200 OK OK,io.reactivex.rxjava3.internal.operators.flowable.FlowableSwitchIfEmpty#35f983a6,[]>
It seems that you expect httpStatus.set(HttpStatus.NO_CONTENT) to be run, and after running, the status can be read with the correct statuscode. I'm not very familiar with reactive, but by looking at Completable.java I don't think the Action that you provide in Completable::fromAction is run right away.
There is probably a more reactive-like way to solve the problem, but I can't help you there. Instead, here's a solution that might work (or at least get you going in the right direction). Just set the value of the status explicitly yourself before returning your Flowable:
public Flowable<Person> personList(AtomicReference<HttpStatus> reference) {
List<Person> repositoryList = List.of();
return repositoryList
.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
if (list.isEmpty()) {
reference.set(HttpStatus.NOT_FOUND);
return Flowable.empty();
}
return Flowable.fromIterable(list);
}));
}
public Maybe<ResponseEntity<Flowable<Person>>> controllerMethod() {
var httpStatus = new AtomicReference<>(HttpStatus.OK);
return Maybe.just(personList(httpStatus))
.map(person -> ResponseEntity.status(httpStatus.get()))
.body(person));
}
Related
I am new to reactive world and trying to write for following logic:
find if entry is there in database corresponding to my service call. If the the data is not there in database then i have to preapre that vendorServiceAreaMapping object and then save.
VendorServiceAreaMapping vendorServiceAreaMapping = new VendorServiceAreaMapping();
vendorServiceAreaMapping.setVendorId(123);
vendorServiceAreaMapping.setClientId(456);
int count= vendorService.findCountByVendorId(vendorServiceAreaMapping.getVendorId(), vendorServiceAreaMapping.getClientId())
if(count ==0){
CityModel cityModel = cityService.getCityByName("Florida");
vendorServiceAreaMapping.setCityId(cityModel.getCityId());
vendorService.save(vendorServiceAreaMapping);
}else{
new VendorServiceAreaMapping();
}
Above code snippet is what i am trying to incorporate using spring reactive way:
public Mono<VendorServiceAreaMapping> createVendorMapping(String invitationName) {
return invitationService.getInvitationDetails(invitationName)
.flatMap(vendorServiceAreaMapping -> {
vendorService.findCountByVendorId(vendorServiceAreaMapping.getVendorId(), vendorServiceAreaMapping.getClientId())// returns integer
.doOnNext(count -> {
if(count == 0){// there is no corresponding entry in database
.flatMap(vendorServiceAreaMapping -> {
return cityService.getCityByName("Florida")
.map(cityModel -> {
vendorServiceAreaMapping.setCityId(cityModel.getCityId());
return vendorServiceAreaMapping;
});
})
.flatMap(vendorServiceAreaMapping -> {
return vendorService.save(vendorServiceAreaMapping);
})
}else{
return Mono.just(new VendorServiceAreaMapping());
}
});
}
}
You just need to create a reactive flow chaining async methods using flatMap.
public Mono<VendorServiceAreaMapping> createVendorMapping(String invitationName) {
return invitationService.getInvitationDetails(invitationName)
.flatMap(vendorServiceAreaMapping ->
vendorService.findCountByVendorId(vendorServiceAreaMapping.getVendorId(), vendorServiceAreaMapping.getClientId())
.flatMap(count -> {
if (count == 0) {
return cityService.getCityByName("Florida")
.flatMap(cityModel -> {
vendorServiceAreaMapping.setCityId(cityModel.getCityId());
return vendorService.save(vendorServiceAreaMapping);
});
} else {
return Mono.just(new VendorServiceAreaMapping());
}
})
);
}
This code assumes that all methods are reactive and return Mono<T>.
My requirement is as follows.
Get ApptReq object which will have apptId. Get Appt object from DB and update Appt object with the data from ApptReq and update the table.
Mono<User> monoUser = retrieveUser();
public Mono<ServerResponse> updateAppt(ServerRequest request) {
return apptRepository.findById(request.bodyToMono(ApptReq.class).map(ApptReq::getApptId)).flatMap(appt -> {
return updateAppt(appt, request.bodyToMono(ApptReq.class)).flatMap(apptRepository::save).flatMap(
res -> ServerResponse.created(URI.create(String.format(APPT_URI_FORMAT, res.getApptId()))).build());
});
}
private Mono<Appt> updateAppt(Appt appt, Mono<ApptReq> apptReq) {
return apptReq.map(req -> {
appt.setNotes(req.getNotes());
return monoUser.map((usr) -> {
appt.setUpdatedBy(usr.getUserId());
return appt;
});
});
}
Here getting error in updateAppt method that
can not convert from Mono<Object> to Mono<Appt>.
Is there any better approach?
You've got it almost. I changed nothing in your updateAppt(ServerRequest request) method but made just a slight adjustment in your updateAppt(Appt appt, Mono<ApptReq> apptReq) method, as follows:
private Mono<Appt> updateAppt(Appt appt, Mono<ApptReq> apptReq) {
return apptReq.flatMap(req -> {
appt.setNotes(req.getNotes());
return retrieveUser().map((usr) -> {
appt.setUpdatedBy(usr.getUserId());
return appt;
});
});
}
Watch out for the apptReq.flatMap instead of your apptReq.map and everything works fine. Give it a try!
Reminder: Be careful with nested Monos in other Monos or more generally said nested Publishers.
Learning RxJava so will be appreciate for any advice.
Need to receive objects when calculation is ready, so I made PublishSubject in models method:
public PublishSubject<BaseUnit> exec(int inputNumber) {
if (unitList.size() > 0) {
for (BaseUnit unit : unitList) {
unit.setInProgress();
}
}
PublishSubject<BaseUnit> subject = PublishSubject.create();
list = new ArrayList<>();
populateList(inputNumber).
subscribeOn(Schedulers.from(Executors.newFixedThreadPool(ThreadPool.getPoolSize())))
.subscribe(calculatedList -> {
list = calculatedList;
for (List<Integer> elem : list) {
for (ListOperationName operationName : ListOperationName.values()) {
ListUnit unit = new ListUnit(operationName, elem, 0);
calculate(unit);
unitList.add(unit);
subject.onNext(unit);
}
}
}, error -> Log.d("ERROR", error.toString()));
return subject;
}
public Observable<ArrayList<List<Integer>>> populateList(int inputNumber) {
return Observable.fromCallable(() -> {
ArrayList<List<Integer>> list = new ArrayList<>();
Integer[] populatedArray = new Integer[inputNumber];
Arrays.fill(populatedArray, insertValue);
list.add(new ArrayList<>(Arrays.asList(populatedArray)));
list.add(new LinkedList<>(Arrays.asList(populatedArray)));
list.add(new CopyOnWriteArrayList<>(Arrays.asList(populatedArray)));
return list;
});
}
And then trying to subscribe in presenter:
public void calculate(int inputNumber) {
fragment.showAllProgressBars();
repository.exec(inputNumber)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.from(Executors.newFixedThreadPool(ThreadPool.getPoolSize())))
.subscribe(unit -> {
Log.d("PRESENTER RESULT", unit.toString());
fragment.setCellText(unit.getViewId(), unit.getTimeString());
}, error -> Log.d("PRESENTER ERROR", error.toString()));
}
This gives me nothing. But if I use ReplaySubject - it gives me all results, but seems like it uses only one thread. So I assume there is somethings wrong I made with subscription and it should be somewhere earlier.
I need to use exactly PublishSubject for giving me results as they are ready using multiple threads.
How to fix that? Or maybe there is other problem?
I'm learning Java with Android by creating Hacker News reader app.
What I'm trying to do is:
Send a request to /topstories, return Observable<List<int>>, emit when
request finishes.
Map each storyId to Observable<Story>
Merge Observables into one entity, which emits List<Story>, when all requests finishes.
And to the code:
private Observable<Story> getStoryById(int articleId) {
BehaviorSubject<Story> subject = BehaviorSubject.create();
// calls subject.onNext on success
JsonObjectRequest request = createStoryRequest(articleId, subject);
requestQueue.add(request);
return subject;
}
public Observable<ArrayList<Story>> getTopStories(int amount) {
Observable<ArrayList<Integer>> topStoryIds = (storyIdCache == null)
? fetchTopIds()
: Observable.just(storyIdCache);
return topStoryIds
.flatMap(id -> getStoryById(id))
// some magic here
}
Then we would use this like:
getTopStories(20)
.subscribe(stories -> ...)
You can try something like that
Observable<List<Integers>> ids = getIdsObservable();
Single<List<Story>> listSingle =
ids.flatMapIterable(ids -> ids)
.flatMap(id -> getStoryById(id)).toList();
Then you can subscribe to that Single to get the List<Story>
Please have a look at my solution. I changed your interface to return a Single for getStoryById(), because it should only return one value. After that, I created a for each Story a Single request and subscribed to all of them with Single.zip. Zip will execute given lambda, when all Singles are finished. On drawback is, that all requestes will be fired at once. If you do not want this, I will update my post. Please take into considerations that #elmorabea solution will also subscribe to the first 128 elements (BUFFER_SIZE = Math.max(1, Integer.getInteger("rx2.buffer-size", 128));), and to the next element when one finishes.
#Test
void name() {
Api api = mock(Api.class);
when(api.getTopStories()).thenReturn(Flowable.just(Arrays.asList(new Story(1), new Story(2))));
when(api.getStoryById(eq(1))).thenReturn(Single.just(new Story(888)));
when(api.getStoryById(eq(2))).thenReturn(Single.just(new Story(888)));
Flowable<List<Story>> listFlowable =
api.getTopStories()
.flatMapSingle(
stories -> {
List<Single<Story>> collect =
stories
.stream()
.map(story -> api.getStoryById(story.id))
.collect(Collectors.toList());
// possibly not the best idea to subscribe to all singles at the same time
Single<List<Story>> zip =
Single.zip(
collect,
objects -> {
return Arrays.stream(objects)
.map(o -> (Story) o)
.collect(Collectors.toList());
});
return zip;
});
TestSubscriber<List<Story>> listTestSubscriber =
listFlowable.test().assertComplete().assertValueCount(1).assertNoErrors();
List<List<Story>> values = listTestSubscriber.values();
List<Story> stories = values.get(0);
assertThat(stories.size()).isEqualTo(2);
assertThat(stories.get(0).id).isEqualTo(888);
assertThat(stories.get(1).id).isEqualTo(888);
}
interface Api {
Flowable<List<Story>> getTopStories();
Single<Story> getStoryById(int id);
}
static class Story {
private final int id;
Story(int id) {
this.id = id;
}
}
I have two requests, second one it dependence in the First so, how to make it in sequence, because there is some check will receive null if it request in parallel
Observable<Map<Integer, SupportedVersion>> supportedVersionObservable = contentAPI
.getSupportedVersionsContent()
.compose(ReactiveUtils.applySchedulers())
.map(supportedVersionsContentContentContainer -> supportedVersionsContentContentContainer.getContent().get(0).getMessage())
.doOnNext(supportedVersionsMap -> {
Timber.i("doOnNext invoked from supported version observable");
for (Map.Entry<Integer,SupportedVersion> entry : supportedVersionsMap.entrySet())
if (Build.VERSION.SDK_INT >= entry.getKey())
model.setSupportedVersion(entry.getValue());
model.setCurrentVersionExpiryDate(model.getSupportedVersion().getCurrentVersionExpiryDate());
if (model.getSupportedVersion() != null)
model.setNewFeaturesSeen(sharedPreferencesManager.isNewFeaturesSeen(model.getSupportedVersion().getAvailableVersions().get(0)));
if (model.isNewFeaturesSeen());
//request data from here
})
.retry(1);
Observable<List<WhatsNew>> getWhatsNewFeature = contentAPI
.getWhatsNewFeature(model.getSupportedVersion().getAvailableVersions().get(0))
.compose(ReactiveUtils.applySchedulers())
.doOnNext(whatsNewList -> {
Timber.i("doOnNext invoked from supported version observable");
if (!whatsNewList.isEmpty())
model.setWhatsNews(whatsNewList);
})
.retry(1);
You can use flatMap for that:
public Observable<List<WhatsNew>> makeRequest {
return contentAPI
.getSupportedVersionsContent()
.flatMap(supportedVersionsMap -> {
//... model initialization
return contentAPI
.getWhatsNewFeature(model.getSupportedVersion().getAvailableVersions().get(0))
.compose(ReactiveUtils.applySchedulers())
.doOnNext(whatsNewList -> {
Timber.i("doOnNext invoked from supported version observable");
if (!whatsNewList.isEmpty())
model.setWhatsNews(whatsNewList);
})
.retry(1);
});
You do not need side-effects. You may hold the model-state in the operators:
#Test
void name() {
ContentApi mock = mock(ContentApi.class);
Observable<Model> modelObservable = mock.getSupportedVersionsContent()
.map(s -> {
// do Mapping
return new Model();
})
.flatMap(model -> mock.getWhatsNewFeature(model)
.map(whatsNews -> {
// Create new model with whatsNews
return new Model();
}), 1);
}
interface ContentApi {
Observable<String> getSupportedVersionsContent();
Observable<List<WhatsNew>> getWhatsNewFeature(Model model);
}
class Model {
}
class WhatsNew {
}
Please have a Look for a detail description of flatMap:
http://tomstechnicalblog.blogspot.de/2015/11/rxjava-operators-flatmap.html?m=0