I'm working on a Spring Boot CRUD RESTful API and i'm trying to define the best way of doing certain things, for instance :
This is my List user by its id endpoint service :
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public Optional<User> listUser(Long id) {
Optional<User> user = repository.findById(id);
if (!user.isPresent()) {
throw new UserNotFoundException(id);
} else {
return repository.findById(id);
}
}
}
And this is another way of writing it :
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public User listUser(Long id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
}
Both ways work but how do i know which is better?
Using java-8 is always a better choice for less code and more readable code.
You can use below tyle of as you mentioned it as your second option.
Using the Optional.orElseThrow() method represents another elegant alternative to the isPresent()-get() pair
You can find out more here
https://dzone.com/articles/using-optional-correctly-is-not-optional
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public User listUser(Long id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
}
Related
I'm looking to write a decorator that takes a very static function and wraps it inside a controller.
Think of it as a global scope utility callable/runnable, so pathvariable/requestbody has to be injected into the parameters. And then it has to automatically be wrapped inside a bean controller with the appropriate getmapping/postmapping to expose it an endpoint
#AutoGetMapping("/users/{id}")
public ResponseEntity<User> getById(#PathVariable long id) {
Optional<User> user = userService.getById(id);
if (user.isPresent()) {
return new ResponseEntity<>(user.get(), HttpStatus.OK);
} else {
throw new RecordNotFoundException();
}
}
gets transformed to
#RestController
public class UserController {
#Autowired
UserService userService;
#GetMapping("users")
public ResponseEntity<List<User>> getAll() {
return new ResponseEntity<>(userService.getAll(), HttpStatus.OK);
}
#GetMapping("users/{id}")
public ResponseEntity<User> getById(#PathVariable long id) {
Optional<User> user = userService.getById(id);
if (user.isPresent()) {
return new ResponseEntity<>(user.get(), HttpStatus.OK);
} else {
throw new RecordNotFoundException();
}
}
}
(maybe even the service layers).
I'm just looking for a place to start. I think im making a mistake in trying to use BeanPostProcessor and BeanDefinitionRegistryPostProcessor to do this. Can someone point me in the right direction on how to start doing this ?
One way to do it could be using the approach described in Interface Driven Controllers article with some additions.
As in the article, we can create an interface with the default annotations. Additionally, we can implement the default methods and enforce the implementation of the certain methods in the service layer using some generics like this:
#RestController
#RequestMapping("/default")
public interface BasicRestController<ID, T, S extends BasicRestService<T, ID>> {
#NonNull S getService();
#GetMapping("/{id}")
default ResponseEntity<T> getById(#PathVariable ID id) {
return getService().getById(id)
.map(ResponseEntity::ok)
.orElseThrow(RecordNotFoundException::new);
}
#GetMapping
default ResponseEntity<List<T>> getAll() {
List<T> results = getService().getAll();
return ResponseEntity.ok(results);
}
}
public interface BasicRestService<T, ID> {
Optional<T> getById(ID id);
List<T> getAll();
}
And then use it in the controller like this:
#RestController
#RequestMapping("/bars")
#RequiredArgsConstructor
public class BarController implements BasicRestController<Long, Bar, BarService> {
private final BarService barService;
#Override
public #NonNull BarService getService() {
return barService;
}
}
Minimal working example can be found here: https://bitbucket.org/kasptom/stack-73744318-interface-driven-controller
I'm working on a spring boot CRUD RESTful API with an User entity that consists of two parameters : name and id. My test framework is JUnit.
The problem i'm facing is that i don't know how to treat a throwable UserNotFound exception on my services unit tests.
I have possible "User not found by {id}" exceptions in my "List user by id", "Delete user by id" and "Update user by id" as you can see (i'll only list two endpoints to make this shorter) :
#Service
public class DeleteUserService {
#Autowired
UserRepository repository;
public void deleteUser(Long id) {
Optional<User> userOptional = repository.findById(id);
if (!userOptional.isPresent()) {
throw new UserNotFoundException(id);
} else {
repository.deleteById(id);
}
}
}
#Service
public class DetailUserService {
#Autowired
UserRepository repository;
public Optional<User> listUser(Long id) {
Optional<User> user = repository.findById(id);
if (!user.isPresent()) {
throw new UserNotFoundException(id);
} else {
return repository.findById(id);
}
}
}
Nothing wrong so far, my endpoints are working fine.
The UserNotFound code is :
#ControllerAdvice
public class UserNotFoundAdvice {
#ResponseBody
#ExceptionHandler(UserNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
String userNotFoundHandler(UserNotFoundException ex) {
return ex.getMessage();
}
}
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super("Could not find user with id " + id + ".");
}
}
The unit tests (the main reason this is being written) :
#RunWith(MockitoJUnitRunner.class)
public class DeleteUserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private DeleteUserService deleteUserService;
#Test
public void whenGivenId_shouldDeleteUser_ifFound(){
User user = new User();
user.setId(89L);
deleteUserService.deleteUser(user.getId());
verify(userRepository).deleteById(user.getId());
}
}
#RunWith(MockitoJUnitRunner.class)
public class DetailUserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private DetailUserService detailUserService;
#Test
public void whenGivenId_shouldReturnUser_ifFound() {
User user = new User();
user.setId(89L);
Optional<User> userMock = Optional.of(user);
when(userRepository.findById(user.getId())).thenReturn(userMock);
Optional<User> expected = detailUserService.listUser(user.getId());
assertThat(expected).isSameAs(userMock);
verify(userRepository).findById(user.getId());
}
}
As you can see, there's something missing in these unit tests code which is the behavior of the UserNotFound. Perhaps it is not properly mocked or something else's missing in the unit tests code??
Would really appreciate if someone could help me with this one! Sorry if the post's too long, i tried my best to explain it!
If I understand you right you need to test the behavior when the user is not found and you throw an exception.
Here is the link about how to test exception: https://www.baeldung.com/junit-assert-exception
And also additionally you can verify that delete by id or find by id weren't invoked:
verify(userRepository, never()).findById(user.getId());
or
verify(userRepository, Mockito.times(0)).findById(user.getId());
and for the deleteById the same
To test that exception handlers were invoked and worked correctly you need integration tests.
There is a controller whose task is to return the user profile to me using the REST API. Code further:
#PostMapping("/me")
public UserProfileResponse getUserProfile(#AuthenticationPrincipal UserAuthenticationPrincipalModel user ) {
return userProfileService.getUserProfile(user.getUserId());
}
I created a model for the User entity. The entity class is created as:
public class User implements UserDetails { ... }
The model has the following structure:
public class UserAuthenticationPrincipalModel extends User {
private String userId;
private String avatarUrl;
public UserAuthenticationPrincipalModel(***.********.entity.User user) {
super(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
this.userId = user.getUserId();
this.avatarUrl = user.getUserPic();
}
// + equals and hashCode
}
In the model, the data that I will ever (or so far plan so) to pull from the AuthPrincipal an authorized user. According to the statement of work, I can’t use the default Principal, I haven’t even tried it. Implementation of UserDetailsService:
#Service
public class UserDetailsServiceImpl extends AbstractMySQLService<User, String, UserRepository> implements UserDetailsService {
private final UserRepository userRepository;
public UserDetailsServiceImpl(final UserRepository userRepository, final UserRepository repository) {
super(repository);
this.userRepository = userRepository;
}
#Override
public UserAuthenticationPrincipalModel loadUserByUsername(final String email) {
User user = userRepository.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException("Invalid username or user not e: " + email);
}
return new UserAuthenticationPrincipalModel(user);
}
}
Error: Null always flies into methods. Made a lot of additions, which are recommended on the Baeldang and this stack - nothing :(
Please, write a comment, if I should add some more information.
UPD 1:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/ca/**").hasRole("COMPANY_ADMIN")
.antMatchers("/d/**").hasRole("DRIVER")
.antMatchers("/u/**").authenticated()
.antMatchers("/sign_up", "/oauth/token", "/swagger-ui.html", "/resources").permitAll();
}
I can give you a few pointers of how to approach this issue.
Ensure you are using org.springframework.security.core.annotation.AuthenticationPrincipal instead of #org.springframework.security.web.bind.annotation.AuthenticationPrincipal (Both should work but just pre-caution because the later is deprecated)
Now the issue is to isolate the problem to ONE of the following areas so you can concentrate there:
Your UserDetailsServiceImpl is not used
Something wrong with getUserProfile method with #AuthenticationPrincipal
user is not associated with a logged in session.
To identify that, replace your public UserProfileResponse getUserProfile method with the following:
[Do not change anything else]
#Autowired
private UserDetailsService userDetailsService;
#PostMapping("/me")
public void getUserProfile(#AuthenticationPrincipal UserDetails user ) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("principal : " + authentication.getPrincipal());
System.out.println("Implementing class of UserDetails: " + authentication.getPrincipal().getClass());
System.out.println("Implementing class of UserDetailsService: " + userDetailsService.getClass());
}
Check the logs and it will tell you where the problem is and if you can't figure out from it, you can post the outcome here for more help
Update: Answers for point 4 given as below in comments.
principal : anonymousUser
Implementing class of UserDetails : class java.lang.String
Implementing class of UserDetailsService : class
Conclusion : endpoint is not protected and user accessing without logging in
Solution : Protect the endpoint by replacing .antMatchers("/u/**").authenticated() with .antMatchers("/api/u/**").authenticated()
I am trying to create a website that allows the user to update, edit, delete, etc., and I have got to the part of Updating or Editing user’s information. I have tried multiple times using different ways, but I cannot seem to get past the error. I am completely oblivious to Optional<> I just don’t get it. I have read https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html, but i dont understand how it should be coded, its just not clicking. If someone could please inform on how it should be coded in my code and please explain it I would be so grateful. Maybe its because im overworked and have not slept, but i cannot seem to correct this error. This is the error i get on the page when i attempt to edit the information for a user:
There was an unexpected error (type=Internal Server Error, status=500).
For input string: "id"
java.lang.NumberFormatException: For input string: id
//Repository
public interface UserRepository extends CrudRepository<User, Integer> {
}
here is the UserService
//UserService
#Service
#Transactional
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository=userRepository;
}
public void saveMyUser(User user) {
userRepository.save(user);
}
public List<User> showAllUsers(){
List<User> users = new ArrayList<User>();
for(User user: userRepository.findAll()) {
users.add(user);
}
return users;
}
public void deleteMyUser(int id) {
userRepository.deleteById(id);
}
public User editUser (int id) {
return userRepository.findById(id);//I also get an error here as well
}
}
here is the controller
//Application Controller
#Controller
public class ApplicationController {
#Autowired
private UserService userService;
// THIS IS WHERE I GET THE ERROR
#RequestMapping("/edit-user")
public String editUser(#RequestParam int id,HttpServletRequest request) {
/* OPTIONAL<?> is this where i would implement the
optional what do i have to put here exactly?
I tried some ways I read about but its not working for me */
request.setAttribute("user", userService.editUser(id));
request.setAttribute("mode", "MODE_UPDATE");
return "welcome";
}
}
Thank you for the help in advance Im a little frustrated with this because I have been trying to correct this error all night.
There are several ways to convert from an option to an entity. You can use the following:
Use get() method:
public User editUser (int id) {
return userRepository.findById(id).get();
}
Use orElse method:
public User editUser (int id) {
/* new User() is stab if user was not found */
return userRepository.findById(id).orElse(new User());
}
Use orElseThrowMethod:
public User editUser (int id) {
/* Throw exception if user was not found*/
return userRepository.findById(id).orElseThrow(IllegalArgumentException::new));
}
As for controller it will be like this:
#RequestMapping("/edit-user")
public String editUser(#RequestParam int id,HttpServletRequest request) {
User user = userService.editUser(id);
request.setAttribute("user", user);
request.setAttribute("mode", "MODE_UPDATE");
return "welcome";
}
Also there similar question for your topic:
Spring Boot. how to Pass Optional<> to an Entity Class
I try to save User to Mongo database in reactive style with Spring WebFlux, but data isn't saved, so I have Java 8, Spring 5 and mongodb.
This is my config:
My entity:
#Document
public class User {
#Id
private String id;
private String username;
private String password;
}
My repository:
public interface UserRepository extends ReactiveMongoRepository<User, String> {
}
My web config:
#Configuration
#EnableWebFlux
public class WebConfig {
#Autowired
private UserRepository userRepository;
#Bean
public RouterFunction userRoutes() {
return route(POST("/admin/users").and(accept(APPLICATION_JSON)),
request -> {
Mono<User> user = request.bodyToMono(User.class);
Mono<User> userMono = user.doOnNext(userRepository::save)
return ServerResponse.().body(userMono, User.class);
});
}
}
May be I have to manual execute subscribe after save to repository, but I havn't found such info in spring reference?
UPD:
But if I extract from request User and pass it to userRepository and return result I have got a successful result to save data to mongo. But I am not sure that it is a good approach:
#Bean
public RouterFunction userRoutes() {
return route(POST("/admin/users").and(accept(APPLICATION_JSON)),
request -> {
User user = request.bodyToMono(User.class).block();
Mono<User> userMono = userRepository.save(user);
return ServerResponse.ok().body(userMono, User.class);
});
}
I have changed doOnNext to flatMap and it works, so it seems to me this is due to the fact that "Transform the item emitted by this Mono asynchronously, returning the value emitted by another Mono" and doOnNext - "Add behavior triggered when the Mono emits a data successfully" and userRepository.save(...) method return Mono that doOnNext add behavious to old Mono and return it(I am loosing response from save method) and flatMap return new Mono which got from save method:
#Bean
public RouterFunction userRoutes() {
return route(POST("/admin/users").and(accept(APPLICATION_JSON)),
request -> {
Mono<User> user = request.bodyToMono(User.class);
Mono<User> userMono = user.flatMap(userRepository::save);
return ServerResponse.status(HttpStatus.CREATED).body(userMono, User.class);
});
}