Not able to return Mono of Boolean with Spring Webflux - java-8

I want to return Mono of Boolean from the below method but getting compilation error with flatMap().
public Mono<Boolean> isMandatory(String country, String compliance,String referenceType) {
Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return Mono.just(customRefFlux.collectList().flatMap(customRefList -> {
customRefList.stream().anyMatch(customRef -> customRef.getType()
.equalsIgnoreCase(referenceType));
}));
}
It is internally calling:
public Flux<CustomReference> getCustomsReferenceRule() {
CustomReference c1 = new CustomReference();
c1.setCrKey("ECU");
c1.setType("MRN");
CustomReference c2 = new CustomReference();
c2.setCrKey("FUE");
c2.setType("FUE");
Flux<CustomReference> custRefFlux = Flux.just(c1, c2);
return custRefFlux;
}
The POJO class
#Data
public class CustomReference {
private String type;
private String crKey;
}

You likely don't want to mix streams and collections unless needed. If I'm following this should be a simple:
public Mono<Boolean> isMandatory(String country, String compliance,String referenceType) {
Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return customRefFlux.any(customRef -> customRef.getType()
.equalsIgnoreCase(referenceType));
}

Flux<CustomReference> customRefFlux = getCustomsReferenceRule();
return customRefFlux.collectList().flatMap(customRefList -> {
List<CustomReference> l = customRefList.stream()
.filter(customRef -> customRef.getType().equalsIgnoreCase(referenceType))
.collect(Collectors.toList());
if (!l.isEmpty())
return Mono.just(Boolean.TRUE);
else
return Mono.just(Boolean.FALSE);
});

Related

How can I read Flux<DataBuffer> content?

I want to read mulitpart/formdata, one part is application/JSON. I can't get them to Map<String,String>, Is there any way to parse Part to String?
private Map<String, String> getFormData(String path, MultiValueMap<String, Part> partMultiValueMap) {
if (partMultiValueMap != null) {
Map<String, String> formData = new HashMap<>();
Map<String, Part> multiPartMap = partMultiValueMap.toSingleValueMap();
for (Map.Entry<String, Part> partEntry : multiPartMap.entrySet()) {
Part part = partEntry.getValue();
if (part instanceof FormFieldPart) {
formData.put(partEntry.getKey(), ((FormFieldPart) part).value());
} else {
String bodyString = bufferToStr(part.content());
formData.put(partEntry.getKey(), bodyString);
}
}
return formData;
}
return null;
}
extra Flux
private String bufferToStr(Flux<DataBuffer> content){
AtomicReference<String> res = new AtomicReference<>();
content.subscribe(buffer -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
res.set(new String(bytes, StandardCharsets.UTF_8));
});
return res.get();
}
Subscribe is async; bufferToStr value may be null?
You could do it in non-blocking way with StringDecoder
Basically you could write your code to return Mono<Map<>>
Note: I'm using Pair class here to return key-value and later collect them to Map
Pair I'm using here is from package org.springframework.data.util.Pair
public Mono<Map<String, String>> getFormData(MultiValueMap<String, Part> partMultiValueMap) {
Map<String, Part> multiPartMap = partMultiValueMap.toSingleValueMap();
return Flux.fromIterable(multiPartMap.entrySet())
.flatMap(entry -> {
Part part = entry.getValue();
if (part instanceof FormFieldPart) {
return Mono.just(
Pair.of(entry.getKey(), ((FormFieldPart) part).value()) // return Pair
);
} else {
return decodePartToString(part.content()) // decoding DataBuffers to string
.flatMap(decodedString ->
Mono.just(Pair.of(entry.getKey(), decodedString))); // return Pair
}
})
.collectMap(Pair::getFirst, Pair::getSecond); // map and collect pairs to Map<>
}
private Mono<String> decodePartToString(Flux<DataBuffer> dataBufferFlux) {
StringDecoder stringDecoder = StringDecoder.textPlainOnly();
return stringDecoder.decodeToMono(dataBufferFlux,
ResolvableType.NONE,
MimeTypeUtils.TEXT_PLAIN,
Collections.emptyMap()
);
}

Mono not executing with schedule

I have a Spring webflux app with the below method.
#Override
public Mono<Integer> updateSetting(int orgId, IntegrationDto dto,
Map<String, Object> jsonMap) {
return retrieveServices(dto.getClientId()).flatMap(services -> {
jsonMap.put("service", services);
return categoryRepository.findCategoryIdCountByName("test", orgId)
.flatMap(categoryIdCount -> {
final ServiceDto serviceInput = new ServiceDto();
if (categoryIdCount == 0) {
return inventoryCategoryRepository.save(InventoryCategory.of("test", orgId))
.flatMap(category -> {
return saveServices(serviceInput, orgId, jsonMap,
category.getCategoryId());
});
} else {
// Some Logc here ...
}
});
}).onErrorResume(e -> {
if (e instanceof WebClientResponseException) {
int statusCode = ((WebClientResponseException) e).getRawStatusCode();
throw new LabServiceException("Unable to connect to the service !", statusCode);
}
throw new ServiceException("Error connecting to the service !");
});
}
private Mono<Services> retrieveServices(final String clientId) {
return webClient.get().uri(props.getBaseUrl() + "/api/v1/services")
.retrieve().bodyToMono(Services.class);
}
private Mono<Integer> saveInventories(ServiceInput serviceInput, int orgId, Map<String, Object> jsonMap,
Long categoryId) {
return refreshInventories(serviceInput, orgId, categoryId).flatMap(reponse -> {
return updateSetting(branchId, jsonMap);
});
}
private Mono<Integer> refreshInventories(ServiceInput serviceInput, int orgId, Long categoryId) {
return inventoryRepository.findAllCodesByTypeBranchId(branchId).collectList().flatMap(codes -> {
return retrieveAvailableServices(Optional.of(serviceInput), categoryId).flatMap(services -> {
List<Inventory> inventories = services.stream()
.filter(inventory -> !codes.contains(inventory.getCode()))
.map(inventoryDto -> toInventory(inventoryDto, branchId)).collect(Collectors.toList());
if (inventories.size() > 0) {
return saveAllInventories(inventories).flatMap(response -> {
return Mono.just(orgId);
});
} else {
return Mono.just(orgId);
}
});
});
}
Here, the updateSettig public method is being invoked from a REST call and all gets executed as expected.
Now, I want to execute the same with a different flow as well like a scheduler.
When I invoke from a scheduler also, It works.
updateSetting(orgId, dto, jsonMap).subscribe();
But, I want to wait until the updateSetting gets executed.
So, tried with the code below.
updateSetting(orgId, dto, jsonMap).flatMap(response -> {
////
});
With the above code, updateSetting method gets invoked, but not getting into the retrieveServices.
return retrieveServices(dto.getClientId()).flatMap(services -> {
You always need to subscribe in the end. So your code should be:
updateSetting(orgId, dto, jsonMap).flatMap(response -> {
////
}).subscribe();

How to write the below code in Java 8 using lambda expressions?

I have the following classes:
package com.test.api;
public class Driver {
private String name;
private int age;
private String gender;
// getters, setters
}
package com.test.api;
import java.util.List;
public class Vehicle {
private String name;
private String make;
private int modelyear;
private List<Driver> drivers;
// getters, setters
}
package com.test.api;
public class Data {
private String driverName;
private String vehicleName;
private String gender;
private boolean isDriverEligible;
private boolean isRecentVehicle;
// getters, setters
}
and I am currently doing this in traditional way without lamdas and streams:
private static List<Data> map(List<Vehicle> vehicleList) {
List<Data> dataList = null;
for (Vehicle vehicle : vehicleList) {
if ("BMW".equalsIgnoreCase(vehicle.getName())) {
dataList = new ArrayList<>();
for (Driver driver : vehicle.getDrivers()) {
if (driver.getAge() > 18 && vehicle.getModelyear() > 2016) {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle.getName());
data.setDriverEligible(true);
data.setRecentVehicle(true);
dataList.add(data);
}
if (driver.getAge() > 18 && vehicle.getModelyear() < 2016) {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle.getName());
data.setDriverEligible(true);
data.setRecentVehicle(false);
dataList.add(data);
}
}
List<Response> response = service.update(dataList, vehicle);
}
}
return dataList;
}
So I wanted to iterate through List of Drivers for each Vehicle and based on the conditions, I need to map some data. And then once all the data is populated to the "Data" List, I am updating it in to a service. Everything looks good but I wanted to refactor it using java 8.How can I do it?
Prior notice: If you are running this against a database, it's HIGHLY RECOMMENDED to do that with some SQL or stored procedures. Messing with data in the application is not considered a good practice unless it's absolutely necessary.
The idea of using stream is to describe filtering and mapping in distinct blocks.
Let's start simple, from the inner loop.
private static List<Data> map(List<Vehicle> vehicleList) {
List<Data> dataList = new ArrayList<>();
vehicleList.stream().filter(vehicle -> "BMW".equalsIgnoreCase(vehicle.getName())).forEach(vehicle -> {
dataList.addAll(
return vehicle.getDrivers().stream()
.filter(driver -> driver.getAge() > 18)
.map(driver -> {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle.getName());
data.setDriverEligible(true);
if (vehicle.getModelyear() > 2016) {
data.setRecentVehicle(true);
return data;
}
if (vehicle.getModelyear() < 2016) {
data.setRecentVehicle(false);
return data;
}
return null;
})
.filter(it -> it != null)
.collect(Collectors.toList())
);
List<Response> response = service.update(dataList, vehicle);
});
return dataList;
}
We have replaced for loops with a stream. Since we are going to add it into a list, we can map the elements into target elements, and then collect them and call addAll(.) to append them to the list.
Then we can make it to the outer loop:
private static List<Data> map(List<Vehicle> vehicleList) {
List<Data> dataList = new ArrayList<>();
vehicleList.stream().filter(vehicle -> "BMW".equalsIgnoreCase(vehicle.getName())).forEach(vehicle -> {
dataList.addAll(
return vehicle.getDrivers().stream()
.filter(driver -> driver.getAge() > 18)
.map(driver -> {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle.getName());
data.setDriverEligible(true);
if (vehicle.getModelyear() > 2016) {
data.setRecentVehicle(true);
return data;
}
if (vehicle.getModelyear() < 2016) {
data.setRecentVehicle(false);
return data;
}
return null;
})
.filter(it -> it != null)
.collect(Collectors.toList())
);
List<Response> response = service.update(dataList, vehicle);
});
return dataList;
}
By simply replacing the for loop with a stream and substitute it with forEach is enough. We can also filter out the unwanted elements before forEach with filter.
Here's is how I would do it:
static void inJava8(List<Vehicle> vehicleList) {
Predicate<Vehicle> isBMW = vehicle -> "BMW".equalsIgnoreCase(vehicle.getName());
Predicate<Vehicle> isRecentvehicle = vehicle -> vehicle.getModelyear() > 2016;
Map<Boolean, List<Vehicle>> isRecentToVehicleMap = vehicleList.stream()
.filter(isBMW::test)
.collect(Collectors.partitioningBy(isRecentvehicle));
isRecentToVehicleMap.get(true).stream()
.forEach(vehicle -> processVehicle(vehicle, Test::recentBMWDataCOnstructor));
isRecentToVehicleMap.get(false).stream()
.forEach(vehicle -> processVehicle(vehicle, Test::notRecentBMWDataCOnstructor));
}
public static void processVehicle(Vehicle vehicle, Function<Driver, Data> function) {
// this predicate should be at class level to avoid creating per vehicle
Predicate<Driver> isAgeEligile = driver -> driver.getAge() > 18;
List<Data> dataList = vehicle.getDrivers()
.stream()
.filter(isAgeEligile::test)
.map(function::apply)
.collect(Collectors.toList());
service.update(dataList, vehicle);
}
public static Data recentBMWDataCOnstructor(Driver driver) {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName("BMW");
data.setDriverEligible(true);
data.setRecentVehicle(false);
return data;
}
public static Data notRecentBMWDataCOnstructor(Driver driver) {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName("BMW");
data.setDriverEligible(true);
data.setRecentVehicle(false);
return data;
}
final Vehicle[] vehicle = new Vehicle[1];
vehicleList.stream()
.filter(v -> "BMW".equalsIgnoreCase(v.getName()))
.map(v -> {vehicle[0] = v; return v;})
.flatMap(v -> v.getDrivers().stream())
.filter(driver -> driver.getAge() > 18)
.map(driver -> {
Data data = new Data();
data.setDriverName(driver.getName());
data.setLuxuryVehicle(true);
data.setVehicleName(vehicle[0].getName());
data.setDriverEligible(true);
data.setRecentVehicle(vehicle[0].getModelyear() > 2016);
return data;
})
.collect(Collectors.toList());

spring data jpa dynamic query which has IN clause

I want to create dynamic query in spring data jpa. Doing many search I can implement it, but I came across a problem when I add IN operator in where clause. I need to check id IN (longlist)
Here is my entity class
#Entity
#Table(name = "view_detail")
public class ViewDetailDom {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
#Column(name = "user_id")
private Long userId;
private String description;
Here is specification builder class and specification class
public class ViewDetailSpecificationsBuilder {
private final List<SearchCriteria> params;
public ViewDetailSpecificationsBuilder() {
params = new ArrayList<SearchCriteria>();
}
public ViewDetailSpecificationsBuilder with(String key, Operation operation, Object value) {
params.add(new SearchCriteria(key, operation, value));
return this;
}
public Specification<ViewDetailDom> build() {
if (params.size() == 0) {
return null;
}
List<Specification<ViewDetailDom>> specs = new ArrayList<Specification<ViewDetailDom>>();
for (SearchCriteria param : params) {
specs.add(new ViewDetailSpecification(param));
}
Specification<ViewDetailDom> result = specs.get(0);
for (int i = 1; i < specs.size(); i++) {
result = Specifications.where(result).and(specs.get(i));
}
return result;
}
}
public class ViewDetailSpecification implements Specification<ViewDetailDom> {
private SearchCriteria criteria = new SearchCriteria();
public ViewDetailSpecification(SearchCriteria searchCriteria) {
this.criteria.setKey(searchCriteria.getKey());
this.criteria.setOperation(searchCriteria.getOperation());
this.criteria.setValue(searchCriteria.getValue());
}
#Override
public Predicate toPredicate(Root<ViewDetailDom> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
String value = criteria.getValue().toString().replaceAll(" ", "%");
if (criteria.getOperation() != null && criteria.getOperation() != Operation.DEFAULT) {
if (criteria.getOperation() == Operation.GREATHERTHANEQUALTO) {
return builder.greaterThanOrEqualTo(root.<String>get(criteria.getKey()), value);
} else if (criteria.getOperation() == Operation.LESSTHANEQUALTO) {
return builder.lessThanOrEqualTo(root.<String>get(criteria.getKey()), value);
} else if (criteria.getOperation() == Operation.EQUAL) {
return builder.equal(root.<String>get(criteria.getKey()), value);
} else if (criteria.getOperation() == Operation.IN) {
Path<Long> view = root.<Long>get(criteria.getKey());
return view.in(criteria.getValue());
}
} else {
if (root.get(criteria.getKey()).getJavaType() == String.class) {
return builder.like(builder.lower(root.<String>get(criteria.getKey())),
"%" + value.toLowerCase() + "%");
} else {
return builder.equal(root.get(criteria.getKey()), value);
}
}
return null;
}
}
This method creates specification builder:
public ViewDetailSpecificationsBuilder createSearchSpecifications(ViewSearch view) {
ViewDetailSpecificationsBuilder builder = new ViewDetailSpecificationsBuilder();
if (StringUtils.isNotBlank(view.getName())) {
builder.with("name", Operation.DEFAULT, view.getName());
}
if (StringUtils.isNotBlank(view.getDescription())) {
builder.with("description", Operation.DEFAULT, view.getDescription());
}
return builder;
}
And finally I do this:
ViewDetailSpecificationsBuilder builder = createSearchSpecifications(view);
builder.with("userId", Operation.DEFAULT, userSessionHelper.getUserId());
builder.with("id", Operation.IN, viewids);
Specification<ViewDetailDom> spec = builder.build();
viewDetailDao.findAll(spec);
But I am getting following error:
"Unaware how to convert value [[5, 7, 8] : java.util.ArrayList] to requested type [java.lang.Long]; nested exception is java.lang.IllegalArgumentException: Unaware how to convert value [[5, 7, 8] : java.util.ArrayList] to requested type [java.lang.Long]"
I have resolved this problem in this way:
ViewDetailSpecification class:
if (criteria.getOperation() == Operation.IN) {
final List<Predicate> orPredicates = new ArrayList<Predicate>();
List<Long> viewIds = (List<Long>) criteria.getValue();
for (Long viewid : viewIds) {
orPredicates.add(builder.or(builder.equal(root.<String>get(criteria.getKey()), viewid)));
}
return builder.or(orPredicates.toArray(new Predicate[orPredicates.size()]));
}
In kotlin I have the same error, I change the ArrayList to Array, with this code:
fun values(): Array<String> {
val elems = arrayListOf<String>()
return elems.toTypedArray()
}
Try you convert ArrayList to array, for java see: make arrayList.toArray() return more specific types

java 8 nested null check list of objects

I am trying to do a nested null check and then clear the values in map in the nested object if the map is not null.
The following is my hypothetical code. I am wondering if this is the right way to do it or is there a more elegant solution to this.
package exp.myJavaLab.Experiments;
import java.util.*;
public class OptionalTest {
public Inner inner;
public static void main(String[] args) {
OptionalTest testObj = new OptionalTest();
Pojo pojo1 = new Pojo();
pojo1.id = 1;
Map<String, String> dataMap = new HashMap<>();
dataMap.put("a","b");
pojo1.dataMap = dataMap;
Pojo pojo2 = new Pojo();
pojo2.id = 2;
Inner inner = new Inner();
inner.pojoList = Arrays.asList(pojo1, pojo2);
testObj.inner = inner;
System.out.println(testObj);
Optional.ofNullable(testObj.inner)
.map(Inner::getPojoList)
.ifPresent(pojos -> pojos.forEach(pojo -> {
if (pojo.getDataMap() != null) {
pojo.getDataMap().clear();
}
}));
System.out.println(testObj);
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("OptionalTest{");
sb.append("inner=").append(inner);
sb.append('}');
return sb.toString();
}
}
class Inner {
public List<Pojo> pojoList;
public List<Pojo> getPojoList() {
return pojoList;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Inner{");
sb.append("pojoList=").append(pojoList);
sb.append('}');
return sb.toString();
}
}
class Pojo {
public Map<String, String> dataMap;
public int id;
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Pojo{");
sb.append("dataMap=").append(dataMap);
sb.append(", id=").append(id);
sb.append('}');
return sb.toString();
}
public Map<String, String> getDataMap() {
return dataMap;
}
public int getId() {
return id;
}
}
In my opinion Collections should never be null.
You could declare your pojoList and dataMap as private and instantiate them.
Your class then needs some add-methods. So you are sure getDataMap() never returns null:
class Pojo {
private Map<String, String> dataMap = new HashMap<>();
public Map<String, String> getDataMap() {
return dataMap;
}
public void add(String key, String value) {
dataMap.put(key, value);
}
}
Then you don't need to check for null:
Optional.ofNullable(testObj.inner)
.map(Inner::getPojoList)
.ifPresent(pojos -> pojos.forEach(pojo -> { pojo.getDataMap().clear(); }));

Resources