Spring Data REST Event not raised in #RepositoryRestController - java

I'm using Spring Boot, Spring Data REST, Spring HATEOAS. I created a #RepositoryRestController:
#Api(tags = "Ticket Entity")
#RepositoryRestController
#PreAuthorize("isAuthenticated()")
public class TicketController extends RevisionController<TransitCertificate> {
private Logger log = LogManager.getLogger();
#Autowired
private LocalValidatorFactoryBean validator;
#Autowired
private TicketService ticketService;
#Autowired
private EnumTranslator enumTranslator;
#SuppressWarnings("rawtypes")
#Autowired
private PagedResourcesAssembler pagedResourcesAssembler;
#Autowired
private MessageSource messageSource;
#Autowired
private Javers javers;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(validator);
}
#PostMapping(path = "/tickets")
public ResponseEntity<?> save(#RequestBody(required = true) #Valid Ticket ticket, PersistentEntityResourceAssembler resourceAssembler) {
return new ResponseEntity<>(resourceAssembler.toResource(ticketService.save(ticket)), HttpStatus.OK);
}
}
I need to intercept the event before the Ticket object is persisted. I created my handler:
#Component
#RepositoryEventHandler(Ticket.class)
public class TicketHandler {
private Logger log = LogManager.getLogger();
#Autowired
private WorkShiftRepository workShiftRepository;
#HandleBeforeCreate
public void handleBeforeCreates(Ticket ticket) {
WorkShift workShift = workShiftRepository.findByAgentUsernameAndEndDateIsNull();
if (workShift != null) {
ticket.setWorkShift(workShift);
}
}
}
and this is my TicketRepository:
#Transactional
#PreAuthorize("isAuthenticated()")
public interface TicketRepository extends PagingAndSortingRepository<Ticket, Long> {
#RestResource(exported = false)
#Override
public <S extends Ticket> Iterable<S> save(Iterable<S> entities);
#RestResource(exported = false)
#Override
public <S extends Ticket> S save(S entity);
#RestResource(exported = false)
#Override
public void delete(Long id);
#RestResource(exported = false)
#Override
public void delete(Ticket entity);
#Query(value = "SELECT MAX(number) FROM Ticket t WHERE t.block=:ticketBlock")
public Long findMaxNumber(#Param("ticketBlock") TicketBlock ticketBlock);
}
as described in the documentation but the event is not emitted. Like described here I'm using the #HandleBeforeCreate annotation. Am I doing something wrong?

Related

Spring Boot - "this.peopleService" is null?

I'm a bit new to Spring Boot and I'm trying to create model/repo/service/serviceImp/controller type of architecture.
After I try to make a this get request:
http://localhost:8080/api/v1/people/name?name=steve
and I get this error (I created a couple of people in DB):
"java.lang.NullPointerException: Cannot invoke \"com.project.Springbootbackend.service.PeopleService.findAllByName(String)\" because \"this.peopleService\" is null\r\n\tat com.project.Springbootbackend.controller.PeopleController.findAllByName(PeopleController.java:24)
This is my code:
People(entity)
#Entity
public class People {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#Column(name = "email")
private String email;
...
//constructor + get/set
PeopleController
#RestController
#RequestMapping("/api/v1/people")
#RequiredArgsConstructor
public class PeopleController {
private PeopleService peopleService;
#GetMapping("/name")
public ResponseEntity<List<People>> findAllByName(#RequestParam String name) {
return ResponseEntity.ok().body(peopleService.findAllByName(name));
}
}
PeopleRepo
public interface PeopleRepository extends JpaRepository<People, Integer> {
List<People> findAllByName(String name);
}
PeopleService
public interface PeopleService {
List<People> findAllByName(String name);
}
PeopleServiceImp
#RequiredArgsConstructor
#Service
public class PeopleServiceImp implements PeopleService {
PeopleRepository peopleRepository;
#Override
public List findAllByName(String name) {
return (List) ResponseEntity.ok(peopleRepository.findAllByName(name));
}
}
Thx guys in advance.
*SOLUTION:
Entity, service & repository is the same.
ServiceImp and controller changes are down belowe:
Controller:
#RestController
#RequestMapping("/api/v1/people")
public class PeopleController {
private PeopleService peopleService;
public PeopleController(PeopleService peopleService) {
this.peopleService = peopleService;
}
#GetMapping("/name")
public ResponseEntity<List<People>> findAllByName(#RequestParam String name) {
return ResponseEntity.ok().body(peopleService.findAllByName(name));
}
}
ServiceImp
#Service
public class PeopleServiceImp implements PeopleService {
private PeopleRepository peopleRepository;
public PeopleServiceImp(PeopleRepository peopleRepository) {
this.peopleRepository = peopleRepository;
}
#Override
public List<People> findAllByName(String name) {
List<People> people = peopleRepository.findAllByName(name);
return people;
}
}
Your constructor does not inject the service, because of the RequiredArgsConstructor (see Link) needs special treatment. Therefore, use final:
#RestController
#RequestMapping("/api/v1/people")
#RequiredArgsConstructor
public class PeopleController {
private final PeopleService peopleService;
#GetMapping("/name")
public ResponseEntity<List<People>> findAllByName(#RequestParam String name) {
return ResponseEntity.ok().body(peopleService.findAllByName(name));
}
}
Same here:
#RequiredArgsConstructor
#Service
public class PeopleServiceImp implements PeopleService {
private final PeopleRepository peopleRepository;
#Override
public List findAllByName(String name) {
return (List) ResponseEntity.ok(peopleRepository.findAllByName(name));
}
}
Additional hint, use a typed list:
#Override
public List<People> findAllByName(String name) {
return ResponseEntity.ok(peopleRepository.findAllByName(name));
}
Try like this:
#Autowired
private PeopleService peopleService;
#Autowired
private PeopleRepository peopleRepository;
You also need to add the #SpringBootApplication annotation in the main class of the application.
Something like that:
#SpringBootApplication
class PeopleApplication {
public static void main(String[] args) {
...
Take a look at this article about automatic dependency injection in Spring:
https://www.baeldung.com/spring-autowire
You missed the autowiring annotation in the controller to inject the service which may make this.peopleService to be null.
#Autowired
private PeopleService peopleService;
You also need to do autowire in your serviceimpl class
#Autowired
private PeopleRepository peopleRepository;

How to update database with spring boot

I am trying to update my database (postgresql) in my spring boot app with some form information that I will get from an angular 8 form. How can I complete the code below to do so ?
PS : I have no access to the front devs, so no access to the angular code.
My Entity :
#Entity
#Data
#Table(name = "person")
#AllArgsConstructor
#NoArgsConstructor
public class PersonEntity implements java.io.Serializable {
#Id
#Column(name = "id_person")
private int idPerson;
#Column(name = "name")
private String name;
#Column(name = "alive")
private Boolean alive;
}
My Mapping class :
#Data
public class PersonForm {
#NotBlank
private String idPerson;
#NotBlank
private String name;
private boolean alive;
}
My Repository :
#Repository
public interface IPersonRepository extends JpaRepository<PersonEntity, String> {
}
My Controller :
#RequestMapping(value = "person")
public class PersonController {
private final PersonService personService;
public PersonController(PersonService personService) {this.personService = personService;}
#PostMapping(value = "savePerson")
#ResponseBody
public ResponseEntity<PersonEntity> savePerson(#RequestBody final PersonForm form) {
return ?
}
}
My Service :
#Transactional
#Service
public class PersonService {
#Autowired
private IPersonRepository personRepository;
public IPersonRepository(IPersonRepository personRepository) {
this.personRepository = personRepository;
}
Maybe this is enough to work.
Have others ways to do it, this is only example that complements your line of thought.
If you get a compilation error or another error, let me know :)
First you need create your object PersonEntity and sent it to your service.
#RequestMapping(value = "person")
public class PersonController {
private final PersonService personService;
public PersonController(PersonService personService) {this.personService = personService;}
#PostMapping(value = "savePerson")
#ResponseBody
public ResponseEntity<PersonEntity> savePerson(#RequestBody final PersonForm form) {
PersonEntity entity = new PersonEntity(form);
entity = personService.save(entity);
return ResponseEntity.ok(entity);
}
}
In your service, you will call the save method from repository implementation.
#Transactional
#Service
public class PersonService {
#Autowired
private IPersonRepository personRepository;
public IPersonRepository(IPersonRepository personRepository) {
this.personRepository = personRepository;
}
public PersonEntity save(PersonEntity entity) {
return personRepository.save(entity);
}
}
After it you will create ResponseEntity in the method PersonController.savePerson

MockHttpServletResponse body empty

I try to test my method addPerson() in my controller, but when I execute the test I have status 200 with an empty body in MockHttpServletResponse. I would like to test the body response with jsonPath from MockMvcResultMatchers but I can't do it while the body is empty.
Here is my test:
#WebMvcTest(PersonController.class)
#ExtendWith(SpringExtension.class)
public class PersonControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private Model model;
#MockBean
private PersonService service;
#Test
public void addPersonTest() throws Exception {
this.mvc.perform(post("/person/add")
.contentType(MediaType.APPLICATION_JSON).content("{\"firstName\": \"Test\",\"lastName\": \"\",\"address\": \"\",\"city\": \"\",\"zip\": \"\",\"phone\": \"\",\"email\": \"\"}"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk());
}
Here is my controller with the method addPerson()
#RequestMapping("/person")
#RestController
public class PersonController {
#Autowired
Model model;
private static final Logger logger = LogManager.getRootLogger();
#Autowired
private PersonService personService;
#GetMapping("/")
public List<Person> allPerson() {
return personService.all();
}
#PostMapping("/add")
public List<Person> addPerson(#RequestBody Person person) {
List<Person> listPerson = this.personService.add(person);
logger.info("Request = #RequestBody = {}", person);
logger.info("Response {}", listPerson);
return listPerson;
}
And here is the service:
#Service
public class PersonService {
#Autowired
private Model model;
public PersonService(Model model2) {
this.model = model2;
}
public List<Person> add(Person person) {
List<Person> listPersons = model.getPersons();
listPersons.add(person);
return listPersons;
}
Thanks for your help.
As you mock the PersonService you have to provide its behaviour otherwise it always returns null. You can use when().thenReturn() from Mockito for this:
#WebMvcTest(PersonController.class)
#ExtendWith(SpringExtension.class)
public class PersonControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private Model model;
#MockBean
private PersonService service;
#Test
public void addPersonTest() throws Exception {
List<Person> personList = Arrays.asList(new Person()); // create list here
when(service.add(any(Person.class)).thenReturn(personList); // mock the behaviour of your PersonService bean
this.mvc.perform(post("/person/add")
.contentType(MediaType.APPLICATION_JSON).content("{\"firstName\": \"Test\",\"lastName\": \"\",\"address\": \"\",\"city\": \"\",\"zip\": \"\",\"phone\": \"\",\"email\": \"\"}"))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk());
}
}

Inject SecurityContext in Service

I have my Application set-up as following:
Resource
#Path("/books")
public class BookResource {
#Inject
BookService bookService;
#Context
SecurityContext securityContext;
#GET
public Response getBooks() {
List<BookDTO> books = bookService.getAllBooks();
return Response.ok(books).build();
}
}
Service
public interface BookService {
List<BookDTO> getAllBooks();
}
ServiceImpl
public class BookServiceImpl implements BookService {
#Context
SecurityContext securityContext;
#Override
public List<BookDTO> getAllBooks() {
BookDTO book1 = new BookDTO("Catcher in the Rye");
BookDTO book2 = new BookDTO("Moby Dick");
return Arrays.asList(new Book[]{book1,book2});
}
}
In my Resource, the SecurityContext is injected and i can fetch the current user.
Is there a way to inject the SecurityContext outside of the Resource (the place where i put my path annotations)? If so, how can I do this?
I want to move my security back to the service and maybe repository too.
Update
I solved it by the following code, but I think it can get a lot better/cleaner.
BaseResource
public class BaseResource {
#Context
SecurityContext securityContext;
public class BaseRequest {
private Principal principal;
public BaseRequest() {
principal = securityContext.getUserPrincipal();
}
public Principal getPrincipal() {
return principal;
}
}
}
BookResource
public class BookResource extends BaseResource {
#Inject
BookService bookService;
#Path("/{id}")
public Response getBookById(#PathParam("id") Long id) {
BookDTO book = bookService.getBookById(new GetBookRequest(id));
return Response.ok(book).build();
}
public Response getAllBooks() {
List<BookDTO > books = bookService.getAllBooks(new GetAllBooksRequest());
return Response.ok(books).build();
}
public class GetBookRequest extends BaseRequest {
private Long id;
public GetBookRequest(Long id) {
super();
this.id = id;
}
public Long getId() {
return id;
}
}
public class GetAllBooksRequest extends BaseRequest {
public GetAllBooksRequest() {
super();
}
}
}
BookService
public interface BookService {
public List<BookDTO> getAllBooks(GetAllBooksRequest request);
public BookDTO getBookById(GetBookRequest request);
}
BookServiceImpl
#Named
public class BookServiceImpl implements BookService {
#Override
public List<BookDTO> getAllBooks(GetAllBooksRequest request) {
Principal principal = request.getPrincipal();
BookDTO book1 = new BookDTO();
book1.setName("Catcher in the Rye");
book1.setId(1L);
BookDTO book2 = new BookDTO();
book2.setName("Moby Dick");
book2.setId(2L);
return Arrays.asList( new BookDTO[]{ book1, book2 });
}
#Override
public BookDTO getBookById(GetBookRequest request) {
Principal principal = request.getPrincipal();
BookDTO book = new BookDTO();
book.setName("Catcher in the Rye");
book.setId(request.getId());
return book;
}
}
You don't need to inject anything. Use SecurityContextHolder insteed.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
var principalName = authentication.getName();

"org.springframework.web.client.HttpClientErrorException: 400 Bad Request" using "RestTemplate.put()"

I wrote this Webservice using Spring Data MVC:
Rest Controller
#RestController
#RequestMapping("/textmessages")
public class TextMessageRestController {
#Autowired
private TextMessageService textMessageService;
#RequestMapping(value = "/send", method = RequestMethod.PUT)
#ResponseStatus(HttpStatus.CREATED)
public void insertTextMessage(#RequestBody TextMessage.TextMessageDTO textMessageDTO) {
textMessageService.save(textMessageDTO);
}
}
Spring Service
#Service
#Transactional
public class TextMessageService {
#Autowired
private TextMessageRepository textMessageRepository;
#Autowired
private UserService userService;
public void save(TextMessage message) {
textMessageRepository.save(message);
}
public void save(TextMessage.TextMessageDTO textMessageDTO) {
save(from(textMessageDTO));
}
public TextMessage from(TextMessage.TextMessageDTO textMessageDTO) {
User sender = userService.from(textMessageDTO.getSender());
User receiver = userService.from(textMessageDTO.getReceiver());
return new TextMessage(receiver, sender, textMessageDTO.getSymmetricKeyEncrypted(), textMessageDTO.getText());
}
}
DTO - a static inner class
//This annotations are from Lombok
#Getter
#Setter
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class SenderReceiverDTO {
private String username;
private String pk;
public static SenderReceiverDTO from(User user) {
SenderReceiverDTO dto = new SenderReceiverDTO(user.username, user.pk);
return dto;
}
}
When I try to consume that REST Service, I get following Exception:
Consuming the rest service
public class RestService {
private static final String REST_STRING = "http://localhost:8080/cchat/";
private static final String TXT_MSG_STRING = REST_STRING + "textmessages/";
private static final String SEND_TXT_MSG = TXT_MSG_STRING + "send/";
private final RestTemplate restTemplate;
public RestService() {
this.restTemplate = new RestTemplate();
}
#SuppressWarnings("unchecked")
public List<TextMessage.TextMessageDTO> loadTextMessages(User.UserIdentifyingDTO userIdentifyingDTO) {
return restTemplate.postForObject(RECEIVE_TXT_MSG, userIdentifyingDTO, List.class);
}
}
i get the excpetion
org.springframework.web.client.HttpClientErrorException: 400 Bad Request
at foo.RestService.sendTextMessage(RestService.java:33)
Tests prove that the server-side is working properly!
Any ideas what may cause the problem?

Categories

Resources