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
Related
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));
}
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>.
I have written a logic using spring reactor library to get all operators and then all devices for each operator (paginated) in async mode.
Created a flux to get all operator and then subscribing to it.
final Flux<List<OperatorDetails>> operatorDetailsFlux = reactiveResourceProvider.getOperators();
operatorDetailsFlux
.subscribe(operatorDetailsList -> {
for (final OperatorDetails operatorDetails : operatorDetailsList) {
getAndCacheDevicesForOperator(operatorDetails.getId());
}
});
Now, for each operator I'm fetching the devices which requires multiple subscriptions to get device mono which gets all pages async by subscribing to the MONO.
private void getAndCacheDevicesForOperator(final int operatorId) {
Mono<DeviceListResponseEntity> deviceListResponseEntityMono = reactiveResourceProvider.getConnectedDeviceMonoWithRetryAndErrorSpec(
operatorId, 0);
deviceListResponseEntityMono.subscribe(deviceListResponseEntity -> {
final PaginatedResponseEntity PaginatedResponseEntity = deviceListResponseEntity.getData();
final long totalDevicesInOperator = PaginatedResponseEntity.getTotalCount();
int deviceCount = PaginatedResponseEntity.getCount();
while (deviceCount < totalDevicesInOperator) {
final Mono<DeviceListResponseEntity> deviceListResponseEntityPageMono = reactiveResourceProvider.getConnectedDeviceMonoWithRetryAndErrorSpec(
operatorId, deviceCount);
deviceListResponseEntityPageMono.subscribe(deviceListResponseEntityPage -> {
final List<DeviceDetails> deviceDetailsList = deviceListResponseEntityPage.getData()
.getItems();
// work on devices
});
deviceCount += DEVICE_PAGE_SIZE;
}
});
}
This code works fine. But my question is it a good idea to subscribe to mono from inside subscribe?
I broke it down to two flows 1st getting all operators and then getting all devices for each operator.
For pagination I'm using Flux.expand to extract all pages.
public Flux<OperatorDetails> getAllOperators() {
return getOperatorsMonoWithRetryAndErrorSpec(0)
.expand(paginatedResponse -> {
final PaginatedEntity operatorDetailsPage = paginatedResponse.getData();
if (morePagesAvailable(operatorDetailsPage) {
return getOperatorsMonoWithRetryAndErrorSpec(operatorDetailsPage.getOffset() + operatorDetailsPage.getCount());
}
return Mono.empty();
})
.flatMap(responseEntity -> fromIterable(responseEntity.getData().getItems()))
.subscribeOn(apiScheduler);
}
public Flux<Device> getAllDevices(final int opId, final int offset) {
return getConnectedDeviceMonoWithRetryAndErrorSpec(opId, offset)
.expand(paginatedResponse -> {
final PaginatedEntity deviceDetailsPage = paginatedResponse.getData();
if (morePagesAvailabile(deviceDetailsPage)) {
return getConnectedDeviceMonoWithRetryAndErrorSpec(opId,
deviceDetailsPage.getOffset() + deviceDetailsPage.getCount());
}
return Mono.empty();
})
.flatMap(responseEntity -> fromIterable(responseEntity.getData().getItems()))
.subscribeOn(apiScheduler);
}
Finally I'm creating a pipeline and subscribing to it to trigger the pipeline.
operatorDetailsFlux
.flatMap(operatorDetails -> {
return reactiveResourceProvider.getAllDevices(operatorDetails.getId(), 0);
})
.subscribe(deviceDetails -> {
// act on devices
});
Below is the process I want to achieve in through my code.
When I receive JsonRecord I need to check if it exists in Redis Cache.
I) If record is NOT present:
1. Create a record in external system DB using REST call.
2. Create Cache Record from JsonRecord and response from step1
3. Create the external System using response from step2
II) If record is present:
1. Update a record in external system DB using REST call.
2. Update the Cache Record from JsonRecord and response from step 1
3. Update the external System using response from step2
NOTE 1: Calls to external systems in step 1 and step 3 are different.
Issue1: When transform() method in MessageTransformationHandler returns null, it directly going back to the switchIfEmpty block in RecordHandler.
Expected: When transform() method returns null, it should go back to calling method.
Issue2: When transform method in MessageTransformationHandler returns null, it directly going back to the switchIfEmpty block in RecordHandler.
Expected: When transform method returns null, it should go back to calling method. then from that method to flatMap condition in RecordHandler class.
#Component
public class RecordHandler {
#Autowired
private contentService contentService;
#Autowired
private RedisService redisService;
#Autowired
private MappingService mappingService;
public Mono<String> handleMessage(JsonRecord record, String recordId, String , long startTime) {
try {
return redisService.getObjectFromRedis(record.getRecordId())
.flatMap(redisResponse -> {
return contentService.updateEvent(record, redisResponse)
.flatMap(dBResponse -> mappingService.createOrUpdateMappingData(dBResponse, record, redisResponse))
.filter(mappingResponse -> mappingResponse!=null)
.flatMap(mappingResponse -> contentService.storeOrUpdatePrices(record, mappingResponse, requestId));
}
}).switchIfEmpty(Mono.defer(() -> {
return contentService.createEvent(record, redisResponse)
.flatMap(dBResponse -> mappingService.createOrUpdateMappingData(dBResponse, record , startTime))
.filter(mappingResponse -> mappingResponse!=null)
.flatMap(mappingResponse -> contentService.storeOrUpdatePrices(record, mappingResponse, requestId));
}));
} catch (Exception e) {
return Mono.error(e);
}
}
}
#Service
public class ContentServiceImpl implements ContentService {
#Override
public Mono<EventType> updateEvent(JsonRecord record, EventsMapping mappingData) {
return Mono.just(record).flatMap(record -> {
MessageTransformationHandler messageHandler = messageTransformationHandlerFactory
.getHelper(record.getClassName());
//transform method can return null value
ContentManagementModelGeneric payload = messageHandler.transform(record, mappingData);
if (payload == null) {
return Mono.empty();
}
return ContentRESTService.createRecord(record, payload);
}).flatMap(sportsDBResponse -> Mono.just(getResponseEvent(record, sportsDBResponse)));
}
}
public class MessageTransformationHandler {
public ContentModel transform(JsonRecord record, MappingData mappingData) {
ContentModel cm = null;
List<MarketType> somethingList = record.getSomething().getSomeList_().stream().map(something -> {
//do something and return null.
return null
})
.filter(something -> something != null)
.collect(Collectors.toList());
if(somethingList == null || somethingList.isEmpty()) {
return null;
}
return cm;
}
}
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.