I am trying to build some custom queries like the documentation is saying, but it is not working.
I got stuck in what i think is the block method and not any data is retrieved.
Here is my repo class:
#Repository
public interface UserRepository extends FirestoreReactiveRepository<User> {
Flux<User> findByCPF(String CPF);
Flux<User> findByEmail(String email);
}
and here is the model:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Document(collectionName = "userCollection")
public class User extends BaseEntity {
#NotNull(message = "{validation.notnull.user.name}")
private String name;
#Email
#NotNull(message = "{validation.notnull.user.email}")
private String email;
#NotNull(message = "{validation.notnull.user.password}")
private String password;
#NotNull(message = "{validation.notnull.user.address}")
private String address;
#CPF
#NotNull(message = "{validation.notnull.user.cpf}")
private String CPF;
#NotNull(message = "{validation.notnull.user.birthDate}")
private Date birthDate;
#NotNull(message = "{validation.notnull.user.role}")
private UserRoleEnum role;
}
this is the method that i'm building that i get stuck:
#Override
public Mono<User> create(User entity) {
var cpf = userRepository.findByCPF(entity.getCPF());
var email = userRepository.findByEmail(entity.getEmail());
if(cpf == null || email == null)
throw new EntityExistException();
else
return super.create(entity);
}
Yeah, it's an override because i have a generic service class:
public class GenericService<T>{
private FirestoreReactiveRepository<T> firestoreReactiveRepository;
public GenericService(FirestoreReactiveRepository<T> firestoreReactiveRepository){
this.firestoreReactiveRepository = firestoreReactiveRepository;
}
#Transactional
public Mono<T> create(T entity){
return firestoreReactiveRepository.save(entity);
}
**** HIDED CONTENT *****
Do you know how custom queries can be built with this FirestoreReactiveRepository ?
Of what types are:
var cpf = userRepository.findByCPF(entity.getCPF());
var email = userRepository.findByEmail(entity.getEmail());
?
You can write them also like:
Flux<User> cpf = userRepository.findByCPF(entity.getCPF());
Flux<User> email = userRepository.findByEmail(entity.getEmail());
I see three problems:
You don't subscribe to the cpf and email fluxes
The condition inside if is always false because both fluxes are not null
if(cpf == null || email == null)
... assuming that cpf and email would not be Flux<User> but User I think the condition should be cpf != null || email != null
The code could look more like this:
#Override
public Mono<User> create(User entity) {
var cpf = userRepository.findByCPF(entity.getCPF()); // Flux<User> cpf = ...
var email = userRepository.findByEmail(entity.getEmail()); // Flux<User> email = ...
return cpf.mergeWith(email)
.<User>handle((user, sink) -> sink.error(new EntityExistException()))
.switchIfEmpty(super.create(entity))
.next();
}
Related
I need to list all the permission available in the system, and on each permission i need to show to particular user which permission is active on him ...
here is table relations:
All Record from Table: authority_master(select * from authority_master)
All Record from Table: users_authority_relation (select * from users_authority_relation;)
All Record from Table: userdetails (select*from userdetails)
Expected JSON Output : if i want to know which and all permission active on each user(basically whose record exists in table "user_authority_relation" table ,(here i want to list all the permissions available in table "authority_master" and on that "isActive" json key is True only if that particular authority exists in table "user_authority_relation"
Basically i need to select userdetails table and join with users_authority_relation , this will give result only who has permission But it will not list all the avaialble permission. i am confused on this how to get like below expected json result
If you are allowed to make multiple database call, you can do this
Get list of Authority authList from database.
Get list of users
For each user do the step 4-5
loop authList and check if the current user's authority list contains the element. if yes set isActive true otherwise false.
Set the authList as currents users permission
As i am also in spring boot learning phase and i found your problem interesting so i attempted to solve your problem
Your entity beans might look like this
user_authority_relation bean
#Table(name="user_authority_relation")
#Data
public class UserAuthRelation implements Serializable {
#Id
#GeneratedValue
Long user_auth_id;
#ManyToOne
#JoinColumn(name="userid")
UserDetails user;
#JoinColumn(name="authorityid")
#OneToOne
AuthorityMaster authority;
}
UserDetailsBean.java
#Entity
#Table(name="userdetails")
#Data
public class UserDetails implements Serializable {
#Id
#GeneratedValue
Long userid;
String name;
#OneToMany(mappedBy="user")
List<UserAuthRelation> userAuths;
}
AuthorityMaster.java
#Entity
#Table(name="authority_master")
#Data
public class AuthorityMaster implements Serializable {
#Id
#GeneratedValue
Long authorityid;
String authority;
String description;
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AuthorityMaster other = (AuthorityMaster) obj;
if (authorityid == null) {
if (other.authorityid != null)
return false;
} else if (!authorityid.equals(other.authorityid))
return false;
return true;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((authorityid == null) ? 0 : authorityid.hashCode());
return result;
}
}
To get desired response, we need to make some classes which can help to create custom response bean which may look like this
#Data
public class CustomResponseJson implements Serializable {
List<UserResponseJson> usersList;
}
#Data
public class UserResponseJson {
Long userId;
List<PermissionObject> permissions;
}
#Data
public class PermissionObject {
String authority;
Long authorityid;
Boolean isactive;
}
Rest Controller
#RestController
public class UsersController {
#Autowired
UserDetailRepository userRepo;
#Autowired
AuthorityRepository authRepo;
#GetMapping("/fetchUserPermissons")
#ResponseBody
public CustomResponseJson fetchUserPermission() {
//find all available users
List<UserDetails> users = userRepo.findAll();
//find all available authorities
List<AuthorityMaster> auths = authRepo.findAll();
//initilizing custom reponse bean
CustomResponseJson res =new CustomResponseJson();
if(users!=null && !users.isEmpty()){
//list of all users in json response
List<UserResponseJson> userJsonReponse = new ArrayList<UserResponseJson>();
for(UserDetails user : users){
UserResponseJson userjson = new UserResponseJson();
userjson.setUserId(user.getUserid());
//prepare list of all authority availed and not availed to user
List<PermissionObject> permissions = new ArrayList<PermissionObject>();
if(user.getUserAuths()!=null && user.getUserAuths().size()>0){
List<AuthorityMaster> tempList = new ArrayList<AuthorityMaster>();
for(UserAuthRelation rel : user.getUserAuths()){
tempList.add(rel.getAuthority());
PermissionObject permObj = new PermissionObject();
permObj.setAuthority(rel.getAuthority().getAuthority());
permObj.setAuthorityid(rel.getAuthority().getAuthorityid());
permObj.setIsactive(true);
permissions.add(permObj);
}
//to find authority which is not assigned to user
List<AuthorityMaster> remainedAuths = auths.stream()
.filter(e -> !tempList.contains(e))
.collect (Collectors.toList());
for(AuthorityMaster auth:remainedAuths){
PermissionObject permObj = new PermissionObject();
permObj.setAuthority(auth.getAuthority());
permObj.setAuthorityid(auth.getAuthorityid());
permObj.setIsactive(false);
permissions.add(permObj);
}
}
userjson.setPermissions(permissions);
userJsonReponse.add(userjson);
}
res.setUsersList(userJsonReponse);
}
return res;
}
}
OUTPUT
You should create one response DTO which can hold all data from different resources(databases, external services)
My Controller response is:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class AxeleStatisticReportDTO implements Serializable {
private static final long serialVersionUID = 1L;
private List<EmailLogDTO> emailAndCount = new ArrayList<>();
private List<IpLogDTO> ipRequestsCount = new ArrayList<>();
}
My controller:
#PostMapping
public ResponseEntity<UserCreateResponse> createUser(#RequestBody #Valid UserCreateRequest userDto,
BindingResult result)
throws InvalidRequestException {
if (result.hasErrors()) {
throw new InvalidRequestException("Request parameter validation failed");
} else {
return ResponseEntity.ok(userService.createUser(userDto));
}
}
Service:
public UserCreateResponse createUser(UserCreateRequest userDto) {
return convertEntityToDto(userRepository.insert(convertDtoToEntity(userDto)));
}
private User convertDtoToEntity(UserCreateRequest userDto) {
return modelMapper.map(userDto, User.class);
}
private UserCreateResponse convertEntityToDto(User user) {
return modelMapper.map(user, UserCreateResponse.class);
}
And the model is :
#Getter
#Setter
#Document("User")
public class User {
#Id
private String id;
#Indexed(unique = true)
private String userName;
private String name;
private String surname;
private String job;
}
Repository is just a class extending MongoRepository.
When I try to insert 2 User with same userName via postman post request, it is adding 2 exactly same item to db even if I specified #Indexed(unique = true) to userName field. Why does this happen and how can I fix it on Java side without breaking indexing function on the field(I want to index userName field to find faster)
Have the same DTO object for POST and PUT methods:
class AcmeRequest {
private String id;
#NotEmpty
private String productCode;
private String description;
}
For POST request I always expect to see productCode field, that's why I specified #NotEmpty annotation but when PUT request received productCode should be optional.
Is it possible some how just to skip #NotEmpty when request is PUT?
Every Hibernate Validator annotation has a groups parameter. Through interfaces, you can control which validations are activated. See more at docs.
In controller level, specify which groups must be activated with the #Validated annotation.
Below, there is a small example from one of my demo projects. I once had the same question as you.
Entity:
#Entity
#Table(name = "tasks")
#Getter #Setter
public class Task
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Null(message = "You can't provide a task ID manually. ID's are automatically assigned by our internal systems.", groups = {TaskInsertValidatorGroup.class})
#NotNull(message = "You must provide an id" , groups = TaskUpdateValidatorGroup.class)
private Integer id;
#NotBlank(message = "Task description cannot be empty")
#Length(max = 255 , message = "Task description length must not exceed 255 characters")
private String description;
#JsonProperty("is_completed")
#Column(name = "is_completed")
private Boolean isCompleted = false;
#CreationTimestamp
#JsonProperty("created_on")
#JsonFormat(pattern="dd-MM-yyyy HH:mm:ss")
#Column(name = "created_on", updatable = false)
private Timestamp creationDate;
#UpdateTimestamp
#JsonProperty("last_modified")
#JsonFormat(pattern="dd-MM-yyyy HH:mm:ss")
#Column(name = "last_modidied")
private Timestamp lastModificationDate;
#Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Task task = (Task) o;
return id.equals(task.id);
}
#Override
public int hashCode()
{
return Objects.hash(id);
}
}
Interfaces:
public interface TaskInsertValidatorGroup {}
public interface TaskUpdateValidatorGroup{}
Controller:
RestController
#RequestMapping("/api")
public class TaskRestController
{
#Autowired
private TaskService taskService;
#GetMapping("/tasks/{id}")
public ResponseEntity<?> getTask(#PathVariable Integer id)
{
return new ResponseEntity<>(taskService.findTask(id),HttpStatus.OK);
}
#GetMapping("/tasks")
public ResponseEntity<?> getTasks()
{
return new ResponseEntity<>(taskService.findAllTasks(),HttpStatus.OK);
}
#PostMapping("/tasks")
public ResponseEntity<?> addTask(#Validated(TaskInsertValidatorGroup.class) #RequestBody Task task)
{
taskService.saveTask(task);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task added")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
#RequestMapping(value = "/tasks" , method = RequestMethod.PATCH)
public ResponseEntity<?> updateTask(#Validated(TaskUpdateValidatorGroup.class) #RequestBody Task task)
{
taskService.updateTask(task);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task Updated")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
#RequestMapping(value = "/tasks/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> removeTask(#PathVariable Integer id)
{
taskService.removeTask(id);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task Deleted")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
}
I have produced a DTO object from 2 microservices. Profile and ProfileCredit. I am able to successfully retrieve a populated DTO object with relevant data. However I am further curious is it possible to query or do conditional filter on the generated DTO object? and if so what is the approach to achieve just that?
For example using 'swagger' this is what gets returned
Is it possible to filter by profileCredit field which is present in the dto but the data is retrieved within separate microservice?
Any help, suggestions or references to any other posts or pages would be truly helpful.
Controller
#GetMapping(path="/profile/search/username/{username}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Page<ProfileCreditDTO>> findProfileByUsername(#PathVariable String username, Pageable pageable) {
Page<ProfileCreditDTO> results= profileCreditService.findProfileBySelectedParameters(username,pageable);
if(results== null){
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} else
return new ResponseEntity<>(results,HttpStatus.OK);
}
Query within profileCreditService
#Query("SELECT p from PROFILES p where lower(p.username) LIKE :username%")
Page<ProfileCreditDTO> findProfileBySelectedParameters(String username, Pageable pageable);
ProfileCreditServiceImpl
public ProfileCreditDTO findProfileCreditByProfileId(final Long profileId){
log.info("Start of findProfileCreditByProfileId method {}",profileId);
ProfileCreditDTO rc= new ProfileCreditDTO();
Profile profile=profileRepository.findOne(profileId);
if(profile == null){
return null; }
CreditDTO creditDto= profileCreditClient.findClientByProfileId(profile.getId());
if(creditDto == null){
return null; }
rc.setProfile(profile);
rc.setCredit(creditDto);
return rc;
}
private ProfileCreditDTO convertProfileToProfileCreditDTO(final Profile theProfile){
if(theProfile == null)
return null;
ProfileCreditDTO theDTO= new ProfileCreditDTO();
theDTO.setProfile(theProfile);
CreditDTO theCreditDto= profileCreditClient.findClientByProfileId(theProfile.getId());
if(theCreditDto != null )
theDTO.setCredit(theCreditDto);
return theDTO;
}
Profile Domain
#Entity(name = "PROFILES")
#Data #NoArgsConstructor #AllArgsConstructor
#ToString
public class Profile implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#Size(min = 2, max = 20)
private String username;
private Integer profileType;
private Integer gender;
private Integer orientation;
private boolean online;
#JsonFormat(pattern="uuuu-MM-dd'T'HH:mm:ss.SSS")
private LocalDateTime created;
#JsonFormat(pattern="uuuu-MM-dd'T'HH:mm:ss.SSS")
private LocalDateTime lastEdited;
Profile Credit DTO
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class ProfileCreditDTO {
//profile fields
private Long profileId;
#Size(min = 2, max = 50)
private String username;
private Integer gender;
private Integer profileType;
private Integer orientation;
private boolean online;
// Credit fields
private Long creditId;
#Column(unique = true)
private double profileCredit;
public void setProfile(final Profile profile) {
this.setProfileId(profile.getId());
this.setUsername(profile.getUsername());
this.setGender(profile.getGender());
this.setProfileType(profile.getProfileType());
this.setOrientation(profile.getOrientation());
this.setOnline(profile.isOnline());
}
public void setCredit(final CreditDTO credit){
this.setCreditId(credit.getId());
this.setProfileCredit(credit.getProfileCredit());
}
ProfileCreditClient (feign)
#Component
#FeignClient(name = "profileCreditService")
public interface ProfileCreditClient {
#GetMapping("/api/credit/profile/{profileId}")
CreditDTO findClientByProfileId(#PathVariable("profileId") Long clientId);
}
Profile Repository Query
#Query("SELECT p from PROFILES p where lower(p.username) LIKE :username%")
Page<Profile> findByAllParameters(#Param("username") String username, Pageable pageable);
I try to make handler and router classes of spring boot webflux. The model class is user class. Codes are
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Document(collection="Users")
public class User {
#Id
private String _id;
#Indexed(unique = true)
private Long id;
#Indexed(unique=true)
private String username;
private String password;
private String email;
private String fullname;
private String role;
}
And below is the handler class of webflux project. In register method, I make the id duplication test codes. But It is totally WRONG.
#Component
public class UserHandler {
#Autowired
private UserReactiveMongoRepository userRepository;
public Mono<ServerResponse> register(ServerRequest request) {
Mono<User> monoUser = request.bodyToMono(User.class);
String id = monoUser.map(u -> u.get_id()).toString();
if(userRepository.existsById(id) == null)
return ServerResponse.ok().build(userRepository.insert(monoUser));
return ServerResponse.ok().build();
}
}
I want to extract the username or id string from Mono of spring webflux.
Any comments will be needed. I am stuck with this part.
One of the things which is wrong here is this like String id = monoUser.map(u -> u.get_id()).toString();. The toString will return you a String like "Mono#13254216541", because you are calling Mono.toString.
One more thing, you shouldn't use the request's data in the body of your function, but in a map or flatMap function.
You could replace it by something like (I'm doing it by head so it might not be 100% syntax corect):
Mono<User> userMono = request.bodyToMono(User.class);//Create a Mono<User>
userMono.map((user) -> { //In the map method, we access to the User object directly
if(user != null && user.getId() != null){
return userRepository.insert(user); // Insert User instead of Mono<User> in your repository
}
return null;
}) //This is still a Mono<User>
.map(insertedUser -> ServerResponse.ok(insertedUser)) //This is a Mono<ServerResponse>
.switchIfEmpty(ServerResponse.ok());
Hope this helps!
a more clean approach would be (i don't like the return of null in the map that others have done) is use the doOnSuccess:
request.bodyToMono(User.class)
.doOnSuccess(user -> return userRepository.insert(user))
.map(user -> ServerResponse.ok(user))
i have left out any error checks but ofc they should be done properly.