Spring Hateos 2 does not make injection when creating lintTo - spring-boot

I try to create a simple controller with spring hateoas.
The controller is the following :
#RestController
public class SearchController {
private final List<Plugin> plugins;
#Autowired
public SearchController(List<Plugin> plugins) {
this.plugins = plugins;
}
#GetMapping("/search")
public CollectionModel<PluginDTO> search(
#RequestParam(value = "name", defaultValue = "") String name) {
List<PluginDTO> pluginsDTO = this.plugins.stream()
.filter(plugin -> {
if(name.isBlank()) { // No filter case.
return true;
}
return plugin.getName().toLowerCase(Locale.ROOT).contains(name.toLowerCase(Locale.ROOT));
})
.map(plugin -> new PluginDTO(plugin.getName(), plugin.getDescription(),
plugin.getCapacities().stream().map(c -> new CapacityDTO(c.getName(), c.getDescription())).toList())
.add(
linkTo(methodOn(SearchController.class).one(plugin.getName())).withSelfRel(),
linkTo(methodOn(SearchController.class).search(name)).withRel("search"))
)
.toList();
Link link = linkTo(methodOn(SearchController.class).search(name)).withSelfRel();
return CollectionModel.of(pluginsDTO, link);
}
#GetMapping("/plugin")
private PluginDTO one(#RequestParam(value = "name") String name) {
return this.plugins.stream().filter(plugin -> plugin.getName().equals(name)).findFirst()
.map(plugin -> new PluginDTO(plugin.getName(), plugin.getDescription(),
plugin.getCapacities().stream().map(c -> new CapacityDTO(c.getName(), c.getDescription())).toList())
.add(
linkTo(methodOn(SearchController.class).one("")).withSelfRel(),
linkTo(methodOn(SearchController.class).search("")).withRel("search"))
)
.orElseThrow(() -> new PluginNotFoundException(name));
}
}
With this code linkTo(methodOn(SearchController.class).get(plugin.getName())).withSelfRel() Spring call the method on() and throw a NPE on this.plugin. It seems that #Autowire is not resolve in this case.
In the official doc : https://spring.io/guides/tutorials/rest/ Injection seems to work
Any idea why this happen ?

Related

How to write custom insert Query Spring Webflux, and return custom response class?

I am new to Reactive programming and I got stuck writing a custom Insert query.
So far I have a FriendshipRepository.java class.
public interface FriendshipRepository extends R2dbcRepository<Friendship, String> {
#Query(value = "INSERT INTO public.friendship(requester_id, addressee_id) values (:requesterid::uuid, :addresseeid::uuid)")
public Mono<Void> insertFriendRequest(
#Param("requesterid") String requesterId,
#Param("addresseeid") String addresseeId
);
}
And a FriendshipController.java class.
#RestController
public class FriendsController {
private final FriendshipRepository friendshipRepository;
public FriendsController(FriendshipRepository friendshipRepository) {
this.friendshipRepository = friendshipRepository;
}
#PostMapping(value = "/request", produces = "application/json")
public Mono<ResponseEntity<RequestResponse>> sendFriendRequest(#RequestBody FriendRequest friendRequest, #AuthenticationPrincipal Mono<User> principal) throws Exception {
String id = principal.map(User::getId).toFuture().get();
return friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.then(Mono.just("NEXT"))
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("Success", ResponseCode.SUCCESS));
}).onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
});
}
}
This is a working example.
But I dont understand why I have to call .then(Mono.just("NEXT")) and create a new Mono to be able to return a custom ResponseEntity<RequestResponse>>. I also tried merge the the whole process. I meen by this at the begining when I get the Id from the ReactiveSpringSecutiryContext that is a blocking line of code and If I know it correctly that is a bad approach in Reactive programming.
I tried this approach but in this case, I can only retrun the Id of the user.
Mono<String> userId = principal.map(User::getId);
return userId.doOnNext(id -> {
friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("SIKER", ResponseCode.SUCCESS));
})
.onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
}).subscribe();
// .doOnSuccess(e -> ServerResponse.noContent().build((new RequestResponse("SIKER", ResponseCode.SUCCESS)), Void.class));
});
How could I rewrite this endpoint? Or does my whole approach inaproptirate?
Thank you in advance for your help.
Probably not the nicest solution, but better then it was.
My query:
public interface FriendshipRepository extends R2dbcRepository<Friendship, String> {
#Query(value = "INSERT INTO public.friendship(requester_id, addressee_id) values (:requesterid::uuid, :addresseeid::uuid) RETURNING id")
public Mono<String> insertFriendRequest(
#Param("requesterid") String requesterId,
#Param("addresseeid") String addresseeId
);
}
My api:
#PostMapping(value = "/request", produces = "application/json")
public Mono<ResponseEntity<RequestResponse>> sendFriendRequest(#RequestBody FriendRequest friendRequest, #AuthenticationPrincipal Mono<User> principal) throws Exception {
String id = principal.map(User::getId).toFuture().get();
return friendshipRepository.insertFriendRequest(id, friendRequest.getUserId()).log()
.map(e -> {
return ResponseEntity.status(HttpStatus.CREATED).body(new RequestResponse("Success", ResponseCode.SUCCESS));
}).onErrorResume(e -> {
return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).body(new RequestResponse("Friend request was unsuccessful ", ResponseCode.REFUSED)));
});
}

Spring boot consume 2 rest and merge some fields

Im new to Spring Boot and got a problem were i need to consume 2 remote Rest services and merge the results. Would need some insight on the right approach.
I got something like this:
{"subInventories":[
{"OrganizationId": 0,
"OrganizationCode":"",
"SecondaryInventoryName":"",
"Description":""},...{}...],
{"organizations":[
{"OrganizationId":0,
"OrganizationCode":"",
"OrganizationName":"",
"ManagementBusinessUnitId":,
"ManagementBusinessUnitName":""}, ...{}...]}
and need to make it into something like this:
{"items":[
{"OrganizationId":0,
"OrganizationCode":"",
"OrganizationName":"",
"ManagementBusinessUnitId":0,
"ManagementBusinessUnitName":"",
"SecondaryInventoryName":"",
"Description":""},...{}...]
got 2 #Entitys to represent each item, Organizations and Inventories with the attributtes like the JSON fields.
EDIT
Currently trying to get matches with Java8 stream()
#GetMapping("/manipulate")
public List<Organization> getManipulate() {
List<Organization> organization = (List<Organization>)(Object) organizationController.getOrganization();
List<SubInventories> subInventories = (List<SubInventories>)(Object) getSuvInventories();
List<Organization> intersect = organization.stream().filter(o -> subInventories.stream().anyMatch(s -> s.getOrganizationId()==o.getOrganizationId()))
.collect(Collectors.toList());
return intersect;
}
found this searching but i got many classes and I don't know if it would be better to just for each organization get the subinventories and put them in a list of maps like
List<Map<String,Object> myList = new ArrayList<>();
//Loops here
Map<String,Object> a = new HashMap<>();
a.put("OrganizationID", 1231242415)...
myList.add(a)
Quite lost in what the right approach is.
EDIT2
Here the classes I'm using.
Organizations
#Entity
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Organization implements Serializable{
//#JsonObject("OrganizationId")
#Id
private Long OrganizationId;
private Long ManagementBusinessUnitId;
private String OrganizationCode,OrganizationName,ManagementBusinessUnitName;
public Organization() {
}
//getters setters
}
SubInventories
#Entity
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubInventories implements Serializable{
#Id
private Long OrganizationId;
private String OrganizationCode,SecondaryInventoryName,Description;
public SubInventories() {
}
//getters and setters
}
Wrapper to unwrapp consume
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Wrapper {
//#JsonProperty("items")
private List<Object> items;
public Wrapper() {
}
public List<Object> getOrganization() {
return items;
}
public void setOrganization(List<Object> organization) {
this.items = organization;
}
}
OrganizationController
#RestController
public class OrganizationController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#GetMapping("/organizations")
public List<Object> getOrganization() {
return getOrganizationInfo();
}
private List<Object> getOrganizationInfo() {
String url = env.getProperty("web.INVENTORY_ORGANIZATIONS");
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url);
builder.queryParam("fields", "OrganizationId,OrganizationCode,OrganizationName,ManagementBusinessUnitId,ManagementBusinessUnitName");
builder.queryParam("onlyData", "true");
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(env.getProperty("authentication.name"),env.getProperty("authentication.password"));
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Wrapper> temp = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<Wrapper>() {});
List<Object> data = temp.getBody().getOrganization();
return data;
}
}
SubInventoryController
#RestController
public class SubInventoryController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#GetMapping("/sub")
public List<Object> getSuvInventories() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("this is private :(");
builder.queryParam("onlyData", "true");
builder.queryParam("expand", "subinventoriesDFF");
builder.queryParam("limit", "999999");
builder.queryParam("fields", "OrganizationId,OrganizationCode,SecondaryInventoryName,Description");
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(env.getProperty("authentication.name"),env.getProperty("authentication.password"));
headers.set("REST-Framework-Version", "2");
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Wrapper> subInventories = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, new ParameterizedTypeReference<Wrapper>() {});
List<Object> data = subInventories.getBody().getOrganization();
return data;
}
}
where I'm right now
#RestController
public class MainController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment env;
#Autowired
private OrganizationController organizationController;
#Autowired
private SubInventoryController subInventoryController;
#GetMapping("/manipulate")
public Map<Organization, List<SubInventories>> getManipulate() {
List<Organization> organizations = (List<Organization>)(Object) organizationController.getOrganization();
List<SubInventories> subInventories = (List<SubInventories>)(Object) subInventoryController.getSuvInventories();
Map<Organization,List<SubInventories>> result = new HashMap<Organization,List<SubInventories>>();
for(Organization organization : organizations) {
List<SubInventories> subInventoryMatched = (List<SubInventories>) subInventories.stream().filter( s -> s.getOrganizationId()== organization.getOrganizationId()).collect(Collectors.toList());
result.put(organizations.get(0), subInventoryMatched);
}
return result;
}
}
From what I understand I need to make a wrapper class for each POJO cause the response looks like this
/organizations
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "adasd",
"OrganizationName": "Hotel Bahía Príncipe Sunlight Costa Adeje",
"ManagementBusinessUnitId": 131231,
"ManagementBusinessUnitName": "asdasfdas"
},
{
"OrganizationId": 2,
"OrganizationCode": "adadas",
"OrganizationName": "Hadasd",
"ManagementBusinessUnitId": 1231,
"ManagementBusinessUnitName": "aewfrqaew"
}]}
and /subInventories
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "asada",
"SecondaryInventoryName": "adfasdfasdgf",
"Description": "pub"
},
{
"OrganizationId": 2,
"OrganizationCode": "asgfrgtsdh",
"SecondaryInventoryName": "B LOB",
"Description": "pub2"
}
]}
If used the generic one with Object I get a java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.demo.model.Organization in the stream().filter and for the merge of the fields another class to get the desired
{
"items": [
{
"OrganizationId": 1,
"OrganizationCode": "asdas",
"OrganizationName": "adsadasd",
"ManagementBusinessUnitId": 1,
"ManagementBusinessUnitName": "asdasdf",
"SecondaryInventoryName": "sfsdfsfa",
"Description": "pub1"
}]}
Tons of classes if i get lots of POJO
I assume the following from the information you provide:
You have two Datatypes (Java classes). They should be merged together to one Java class
You have to load this data from different sources
Non of the classes are leading
I can provide you some example code. The code is based on the previos adoptions. This will give you an idea. It's not a simple copy and paste solution.
At first create a class with all fields you want to include in the result:
public class Matched {
private Object fieldA;
private Object fieldB;
// Some getter and Setter
}
The Basic idea is that you load your data. Than find the two corresponding objects. After that do your matching for each field.
public List<Matched> matchYourData() {
// load your data
List<DataA> dataAList = loadYourDataA();
List<DataB> dataBList = loadYourDataB();
List<Matched> resultList = new ArryList<>();
for (dataA: DataA) {
DataB dataB = dataBList.stream()
.filter(data -> data.getId() == dataA.getId())
.findFirst().orElseThrow();
// Now you have your data. Let's match them.
Matched matched = new Matched();
matched.setFieldA(dataB.getFieldA() == dataA.getFieldA() ? doSomething() : doSomethingElse());
// Set all your fields. Decide for everyone the matching strategy
resultList.add(matched);
}
return resultList;
}
This is a quite simple solution. Of course you can use Tools like Mapstruct for mapping purpose. But this depends on your environment.

Cannot remove attributes in ldap with spring ldap

we need to make a spring boot project that works with spring ldap.
every things is good.But when we remove a member from a group,the member deleted form group (i see it in debug mode in a Setmembers) but, in ldap(Oracle Internet Directory) that member exists!
Please help me!
//Group Entry
#Entry(objectClasses = {"top", "groupOfUniqueNames", "orclGroup"}, base = "cn=Groups")
public final class Group {
#Id
private Name dn;
#Attribute(name = "cn")
private String name;
private String description;
private String displayName;
#Attribute(name = "ou")
private String ou;
#Attribute(name = "uniqueMember")
private Set<Name> members;
public void addMember(Name newMember) {
members.add(newMember);
}
public void removeMember(Name member) {
members.remove(member);
}
//Custom LdapUtils
public class CustomLdapUtils {
private static final String GROUP_BASE_DN = "cn=Groups";
private static final String USER_BASE_DN = "cn=Users";
public Name buildGroupDn(String name) {
return LdapNameBuilder.newInstance(GROUP_BASE_DN)
.add("cn","Charts")
.add("cn",name)
.build();
}
private static final CsutomLdapUtils LDAP_UTILS = new CsutomLdapUtils ();
private CsutomLdapUtils () {
}
public Name buildPersonDn(String name) {
return LdapNameBuilder.newInstance(USER_BASE_DN)
.add("cn", name)
.build();
}
}
//Controller
#DeleteMapping(value = "/memberOfGroup", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> removeMemberFromGroup(#RequestBody Map<String,String> map) throws NamingException {
List<Group> groupToFind = ldapSearchGroupsService.getGroupByCn(map.get("groupName"));
List<User> userToFind = ldapSearchUserService.getAllUserByUserName(map.get("userName"));
if (groupToFind.isEmpty()) {
//TODO : Group no found!
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
for (Group group1 : groupToFind) {
group1.removeMember(userToFind.stream().findAny().get().getDn());
//ldapBindGroupService.deleteMemberFromGroup(group1);
DirContextOperations ctx = ldapTemplate.lookupContext(CustomLdapUtils.getInstance().buildGroupDn(map.get("groupName")));
ctx.removeAttributeValue("uniqueMember",map.get("userName"));
ctx.rebind(CustomLdapUtils.getInstance().buildGroupDn(map.get("groupName")),map.get("groupName"));
ldapTemplate.modifyAttributes(ctx);
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
Is some problem in code? or need some methods?
Finally after several search and debug,i found the problem!
In each ldap env,after every changes,the directory must be commit and apply.
In above code,i implemented that,but not in true way!
Best way is here:
#DeleteMapping(value = "/membersOfGroup", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> removeMemberFromGroup(#RequestBody Map<String,String> map) {
List<Group> groupToFind = ldapSearchGroupsService.getGroupByCn(map.get("groupName"));
List<User> userToFind = ldapSearchUserService.getAllUserByUserName(map.get("userName"));
if (groupToFind.isEmpty()) {
//TODO : Group no found!
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
for (Group group1 : groupToFind) {
group1.removeMember(userToFind.stream().findAny().get().getDn());
DirContextOperations ctx = ldapTemplate.lookupContext(CustomLdapUtils.getInstance().buildGroupDn(map.get("groupName")));
ctx.removeAttributeValue("member",CustomLdapUtils.getInstance().buildPersonDn(map.get("userName")));
//True way
ldapTemplate.update(group1);
}
return new ResponseEntity<>(HttpStatus.OK);
}
}

REST endpoint works when directly accessed, but not when accessed via WebClient

Note: The following is an edit/revision to a similar post/question, in an attempt to better identify my issue/question, and to provide better code samples demonstrating my issue.
Add Note: Code example has been revised to include working code.
I have two endpoints in two routers in the same spring reactive application. The first (/v2/DemoPOJO) appears to work correctly. The second (/v2/DemoClient/DemoPOJO), which uses WebClient to delegate to /v2/DemoPOJO appears to "do nothing" (although the logged output shows that DemoClientHandler.add() and DemoClient.add() are being invoked).
When I do a POST request to the /v2/DemoPOJO endpoint, the doFirst(), doOnSuccess(), and doFinally() are invoked and output the appropriate text (in "real life", a row is added to the repository).
When I do a POST request to the /v2/DemoClient/DemoPOJO endpoint, it returns a 200 OK status, but none of the expected text is output (in "real life", nothing gets added to the repository).
The following files support the /v2/DemoPOJO endpoint...
Router class implementation for DemoPOJO...
#Configuration
public class DemoPOJORouter {
private Logger logger = LoggerFactory.getLogger(DemoPOJORouter.class);
#Bean
public RouterFunction<ServerResponse> demoPOJORoute(DemoPOJOHandler requestHandler) {
logger.debug("DemoPOJORouter.demoPOJORoute( DemoPOJOHandler )");
return nest(path("/v2"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
Handler class implementation for DemoPOJO...
#Component
public class DemoPOJOHandler {
private Logger logger = LoggerFactory.getLogger(DemoPOJOHandler.class);
public Mono<ServerResponse> add(ServerRequest request) {
logger.debug("DemoPOJOHandler.add( ServerRequest )");
return request.bodyToMono(DemoPOJO.class).doFirst(() -> System.out.println("-> doFirst()."))
.doOnSuccess(demoPOJO -> System.out.println("Received >> " + demoPOJO.toString()))
.then(ServerResponse.accepted().build())
.doOnError(e -> System.out.println("-> doOnError()"))
.doFinally(demoPOJO -> System.out.println("-> doFinally()"));
}
}
DemoPOJO implementationj...
public class DemoPOJO {
private Logger logger = LoggerFactory.getLogger(DemoPOJO.class);
public static final String DEF_NAME = "DEFAULT NAME";
public static final int DEF_VALUE = 99;
private int id;
private String name;
private int value;
public DemoPOJO(#JsonProperty("id") int id, #JsonProperty("name") String name, #JsonProperty("value") int value) {
logger.debug("DemoPOJO.DemoPOJO( {}, {}, {} )", id, name, value);
this.id = id;
this.name = name;
this.value = value;
}
/*
* setters and getters go here
*/
public String toString() {
logger.debug("DemoPOJO.toString()");
StringBuilder builder = new StringBuilder();
builder.append(id);
builder.append(" :: ");
builder.append(name);
builder.append(" :: ");
builder.append(value);
return builder.toString();
}
}
The following files support the /v2/DemoClient/DemoPOJO endpoint...
Router implementation for DemoClient...
#Configuration
public class DemoClientRouter {
private Logger logger = LoggerFactory.getLogger(DemoClientRouter.class);
#Bean
public RouterFunction<ServerResponse> clientRoutes(DemoClientHandler requestHandler) {
logger.debug("DemoClientRouter.route( DemoClientHandler )");
return nest(path("/v2/DemoClient"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
}
}
Handler implementation for DemoClient...
#Component
public class DemoClientHandler {
private Logger logger = LoggerFactory.getLogger(mil.navy.demo.demopojo.DemoPOJOHandler.class);
#Autowired
DemoClient demoClient;
public Mono<ServerResponse> add(ServerRequest request) {
logger.debug("DemoClientOHandler.add( ServerRequest )");
// THIS CODE
return request.bodyToMono(DemoPOJO.class).flatMap(demoPOJO -> demoClient.add(demoPOJO))
.then(ServerResponse.accepted().build());
// REPLACES THIS CODE
/*
return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
.then(ServerResponse.ok().build())
.switchIfEmpty(ServerResponse.badRequest().build());
*/
}
}
WebClient implementation for DemoClient...
#Component
public class DemoClient {
private Logger logger = LoggerFactory.getLogger(DemoClient.class);
private final WebClient client;
public DemoClient() {
client = WebClient.create();
}
public Mono<Boolean> add(DemoPOJO demoPOJO) {
logger.debug("DemoClient.add( DemoPOJO )");
logger.debug("DemoClient.add() >> DemoPOJO -> {}", demoPOJO.toString());
return client.post().uri("http://localhost:8080/v2/DemoPOJO")
.accept(MediaType.APPLICATION_JSON)
.syncBody(demoPOJO)
.exchange()
.flatMap(response -> response.bodyToMono(Boolean.class));
}
}
here is where i guess your problem is.
return request.bodyToMono(DemoPOJO.class)
.doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
doOnSuccess takes a consumer, a consumer returns void not Mono<Void>.
Here is the verbose usage of a Consumer.
Mono.just("hello")
.doOnSuccess(new Consumer<String>() {
#Override
public void accept(String s) {
// See here, it returns void
}
});
Lets look at some examples:
Mono<String> helloWorld = Mono.just("Hello")
.doOnSuccess(string -> {
// This will never be executed because
// it is just declared and never subscribed to
Mono.just(string + " world");
});
helloWorld.doOnSuccess(string -> {
// This will print out Hello
System.out.println(string);
});
Mono<String> hello = Mono.just("Hello")
.doOnSuccess(string -> {
// This will print out Hello World
System.out.println(string + " World");
});
// hello hasn't been changed
hello.map(string -> {
// This will also print out Hello World
System.out.println(string + " World");
});
// This prints hello world to after we mapped it
Mono<String> helloworld = Mono.just("hello")
.map(s -> s + " World")
.doOnSuccess(System.out::println);
// Now this is what you are essentially doing
// See how this is wrong?
Mono<DemoPOJO> demoPOJO = request.bodyToMono(DemoPOJO.class)
.doOnSuccess( demoPOJO -> Mono.empty() );
you are calling demoClient#add that returns a Mono<Void> here you are breaking the chain because nothing is chained onto the Mono that it is returning so it never gets subscribed to because it is not in the event chain.
return request.bodyToMono(DemoPOJO.class)
.map( demoPOJO -> {
return demoClient.add(demoPOJO);
});
If you change it to map it will probably work. What is happening then is that it is taking a Mono<DemoPojo> and "mapping" it to a Mono<Void> that is returned by your add function. And suddenly it is in the event chain (callback).

Stream on Iterable in Spring data jpa transactional

I am using Java StreamSupport to stream on Iterable return from repository method inside of #Transactional method in service layer.
Svc:
#Transactional(readOnly = true)
public Map<String, Map<String, Object>> getAllProjects() {
return StreamSupport.stream(projectRepo.findAllByOrderByNameAsc().spliterator(), true)
.collect(
LinkedHashMap::new,
(map, projectItem) -> map.put(projectItem.getName(), populateProjectItem(projectItem)),
(map, projectMap) -> map.putAll(projectMap)
);
}
private Map<String, Object> populateProjectItem(final Project project) {
return ImmutableMap.of(
"id", project.getId(),
"name", project.getName(),
"taskList", project.getTaskList().stream()
.map(taskItem -> ImmutableMap.of(
"id", taskItem.getId()
)
.collect(Collectors.toList())
);
}
I am not sure what I am doing wrong but it gives me nullpointer on Task Id in test case. project.getTaskList() is lazy fetch loaded.
Am i missing something ?

Resources