Spring security configuration in spring boot not working as expected [duplicate] - spring-boot

This question already has answers here:
Springboot Security hasRole not working
(3 answers)
Closed 1 year ago.
I am using spring security with spring boot. But my spring security configuration is now working as expected.
Note : H2 database is running as a separate database in server mode ( Not in embedded mode ).
Here are my project details :
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Application.properties
server.port=8085
spring.datasource.url=jdbc:h2:tcp://localhost:9092/mem:testdb
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
EmployeeeResource.java
#RestController
#RequestMapping("/employee")
#Slf4j
public class EmployeeResource {
#Autowired
EmployeeRepository employeeRepository;
#GetMapping(path = "/greetEmployee", produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHello() {
return "Hello Employee !!!";
}
#GetMapping(path = "/getAllEmployees", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<List<Employee>> getAllEmployee() {
List<Employee> employeeList = employeeRepository.findAll();
return new ResponseEntity<>(employeeList, HttpStatus.OK);
}
#GetMapping(path = "/getEmployee/{employeeId}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<Employee> getEmployee(#PathVariable("employeeId") int employeeId) {
Optional<Employee> optionalEmployee = employeeRepository.findByEmployeeId(employeeId);
if (optionalEmployee.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(optionalEmployee.get(), HttpStatus.FOUND);
}
#PostMapping(path = "/createEmployee", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<HttpStatus> createEmployee(#RequestBody Employee employee) {
Random random = new Random();
employee.setEmployeeId(random.nextInt(9999));
employeeRepository.save(employee);
log.info("Created employee with Id : {}", employee.getEmployeeId());
return new ResponseEntity<>(HttpStatus.CREATED);
}
#PostMapping(path = "/createEmployees", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<String> createEmployees(#RequestBody List<Employee> employeeList) {
int count = 0;
Random random = new Random();
for (Employee employee : employeeList) {
employee.setEmployeeId(random.nextInt(999999));
employeeRepository.save(employee);
log.info("Created employee with Id : {}", employee.getEmployeeId());
count++;
}
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("countOfObjectCreated", String.valueOf(count));
return ResponseEntity.status(HttpStatus.CREATED).headers(responseHeaders).build();
}
#PutMapping(path = "/updateEmployee/{employeeId}", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<HttpStatus> updateCustomer(#PathVariable("employeeId") int employeeId, #RequestBody Employee employee) {
Optional<Employee> optionalDbEmployee = employeeRepository.findByEmployeeId(employeeId);
if (optionalDbEmployee.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Employee dbEmployee = optionalDbEmployee.get();
dbEmployee.setFirstName(employee.getFirstName());
dbEmployee.setLastName(employee.getLastName());
dbEmployee.setExtension(employee.getExtension());
dbEmployee.setEmail(employee.getEmail());
dbEmployee.setOfficeCode(employee.getOfficeCode());
dbEmployee.setReportsTo(employee.getReportsTo());
dbEmployee.setJobTitle(employee.getJobTitle());
return new ResponseEntity<>(HttpStatus.OK);
}
#DeleteMapping(path = "/deleteEmployee/{employeeId}")
public ResponseEntity<HttpStatus> deleteCustomer(#PathVariable("employeeId") int employeeId) {
employeeRepository.deleteById(employeeId);
log.info("Employee with employee id {} Deleted successfully.", employeeId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Employee.java
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder
#Entity
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#Column(name = "EMPLOYEE_ID")
int employeeId;
#Column(name = "LAST_NAME")
String lastName;
#Column(name = "FIRST_NAME")
String firstName;
#Column(name = "EXTENSION")
String extension;
#Column(name = "EMAIL")
String email;
#Column(name = "OFFICE_CODE")
int officeCode;
#Column(name = "REPORTS_TO")
Integer reportsTo;
#Column(name = "JOB_TITLE")
String jobTitle;
}
OfficeResource.java
#RestController
#RequestMapping("/office")
#Slf4j
public class OfficeResource {
#Autowired
OfficeRepository officeRepository;
#GetMapping(path = "/greetOffice", produces = MediaType.TEXT_PLAIN_VALUE)
public String sayHello() {
return "Hello Office !!!";
}
#GetMapping(path = "/getAllOffices", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<List<Office>> getAllOffices() {
List<Office> officeList = officeRepository.findAll();
return new ResponseEntity<>(officeList, HttpStatus.OK);
}
#GetMapping(path = "/getOffice/{officeCode}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<Office> getOffice(#PathVariable("officeCode") int officeCode) {
Optional<Office> optionalOffice = officeRepository.findByOfficeCode(officeCode);
if (optionalOffice.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(optionalOffice.get(), HttpStatus.FOUND);
}
#PostMapping(path = "/createOffice", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<HttpStatus> createOffice(#RequestBody Office office) {
Random random = new Random();
office.setOfficeCode(random.nextInt(999));
officeRepository.save(office);
log.info("Created office with office code : {}", office.getOfficeCode());
return new ResponseEntity<>(HttpStatus.CREATED);
}
#PostMapping(path = "/createOffices", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<String> createOffices(#RequestBody List<Office> officeList) {
int count = 0;
Random random = new Random();
for (Office office : officeList) {
office.setOfficeCode(random.nextInt(999));
officeRepository.save(office);
log.info("Created office with office code : {}", office.getOfficeCode());
count++;
}
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("countOfObjectCreated", String.valueOf(count));
return ResponseEntity.status(HttpStatus.CREATED).headers(responseHeaders).build();
}
#PutMapping(path = "/updateOffice/{officeCode}", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<HttpStatus> updateOffice(#PathVariable("officeCode") int officeCode, #RequestBody Office office) {
Optional<Office> optionalDbOffice = officeRepository.findByOfficeCode(officeCode);
if (optionalDbOffice.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Office dbOffice = optionalDbOffice.get();
dbOffice.setCity(office.getCity());
dbOffice.setPhone(office.getPhone());
dbOffice.setAddressLine1(office.getAddressLine1());
dbOffice.setAddressLine2(office.getAddressLine2());
dbOffice.setState(office.getState());
dbOffice.setCountry(office.getCountry());
dbOffice.setPostalCode(office.getPostalCode());
dbOffice.setTerritory(office.getTerritory());
return new ResponseEntity<>(HttpStatus.OK);
}
#DeleteMapping(path = "/deleteOffice/{officeCode}")
public ResponseEntity<HttpStatus> deleteOffice(#PathVariable("officeCode") int officeCode) {
officeRepository.deleteById(officeCode);
log.info("Office with office code {} Deleted successfully.", officeCode);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Office.java
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder
#Entity
#Table(name = "OFFICE")
public class Office {
#Id
#Column(name = "OFFICE_CODE")
int officeCode;
#Column(name = "CITY")
String city;
#Column(name = "PHONE")
String phone;
#Column(name = "ADDRESS_LINE1")
String addressLine1;
#Column(name = "ADDRESS_LINE2")
String addressLine2;
#Column(name = "STATE")
String state;
#Column(name = "COUNTRY")
String country;
#Column(name = "POSTAL_CODE")
String postalCode;
#Column(name = "TERRITORY")
String territory;
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select username,password,enabled from users where username = ?")
.authoritiesByUsernameQuery("select username,authority from authorities where username = ?")
.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/employee/createEmployee", "/employee/createEmployees", "/employee/updateEmployee/**", "/employee/deleteEmployee/**").hasRole("Admin")
.antMatchers("/office/createOffice", "/office/createOffices", "/office/updateOffice/**", "/office/deleteOffice/**").hasRole("Admin")
.antMatchers("/employee/getEmployee/**", "/employee/getAllEmployees").hasAnyRole("Admin","User")
.antMatchers("/office/getOffice/**", "/office/getAllOffices").hasAnyRole("Admin","User")
.and().formLogin();
}
After hitting the url "http://localhost:8085/employee/getEmployee/1002" from browser i am getting login form and after enter the credentials for "User1" who is "Admin" user and am getting "Forbidden, status=403" error.
Need Help.

hasRole and hasAnyRole prefix your strings with ROLE_. Use hasAuthoritiy or hasAnyAuthority instead. See spring docs

Related

Error locating String field in Spring Boot

I'm trying to find a company by its CNPJ(Brazilian corporate tax payer registry number) in a DB (H2), but it's returning an error
{
"timestamp": "2022-03-30T19:30:23.823+00:00",
"status": 404,
"error": "Not Found",
"path": "/companies/cnpj/30101554000146"
}
I've tried other alternatives using:
http://localhost:8080/companies/cnpj/'30.101.554/0001-46', http://localhost:8080/companies/cnpj/"30.101.554/0001-46",
but the error persists. I implemented like this :
#Entity
#Table(name = "company")
public class Company implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#CNPJ
private String cnpj;
//skipped
}
public interface CompanyRepository extends JpaRepository<Company,Long> {
Optional<Company> findByCnpj(String cnpj);
}
public class CompanyDTO {
private Long id;
private String name;
private String cnpj;
//skipped
}
#Service
#Transactionalpublic class CompanyService {
#Autowired
private CompanyRepository companyRepository;
#Transactional(readOnly = true)
public CompanyDTO findById(Long id) {
Company resultado = companyRepository.findById(id).get();
CompanyDTO dto = new CompanyDTO(resultado);
return dto;
}
#Transactional(readOnly = true)
public CompanyDTO findByCnpj(String cnpf) {
Optional<Company> resultado = companyRepository.findByCnpj(cnpf);
CompanyDTO dto = new CompanyDTO(resultado.get());
return dto;
}
}
#RestController
#RequestMapping(value = "/companies")public class CompanyController {
#Autowired
private CompanyService companyService;
#GetMapping(value = "/{id}")
public CompanyDTO findById(#PathVariable Long id) {
return companyService.findById(id);
}
#GetMapping(value = "/cnpj/{cnpj}")
public CompanyDTO findByCnpj(#PathVariable String cnpj) {
return companyService.findByCnpj(cnpj);
}
}
The expected output would be:
[
{"id": 1,
"nome": "Company 123",
"cnpj": "30.101.554/0001-46"
}
]
UPDATE:
I changed #GetMapping(value = "/cnpj/{cnpj}") to #GetMapping(value = "/cnpj/**") and:
#GetMapping(value = "/cnpj/**")
public CompanyDTO findByCnpj(HttpServletRequest request) {
return companyService.findByCnpj(request.getRequestURI().split(request.getContextPath() + "/cnpj/")[1]);
}
Works for me! Thanks
As explained here, pathParams with slashes can be realy tricky while using spring-boot. This article explains pretty well what to do to avoid getting an error 404 when your pathVariable has a slash.

How to add a new role?

I have a user with admin and user roles, now I need to add ROLE_SUPPORT and restrict this role to only reading the list of users, how can I do this?
public class UserController {
#Autowired
UserService userService;
#RequestMapping(value = "getAll", method = RequestMethod.POST)
public List<User> getUsers() throws IOException {
return userService.getUsers();
}
#PostMapping("save")
#ResponseStatus(HttpStatus.OK)
public void save(#RequestBody User user) {
userService.save(user);
}
#RequestMapping(value = "delete", method = RequestMethod.POST)
public void delete(#RequestBody User user) {
userService.delete(user);
}
#RequestMapping(value = "getUser", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
#ResponseBody
public User getUser(#RequestBody RequestDto requestDto) throws IOException {
return userService.getUser(requestDto.getId());
}
I suppose a new method should be added to this controller, but I could be wrong
public class User extends BaseEntity<Integer> {
public enum Roles {
ADMIN
}
private String firstName;
private String lastName;
#Column(name = "username")
private String username;
#Convert(converter = PurshasedProductConverter.class)
private List<PurshasedProduct> purshasedProducts;
private String email;
private String activationCode;
#Convert(converter = AttachmentConverter.class)
private Attachment userAvatar;
public Attachment getUserAvatar() {
return userAvatar;
}
public void setUserAvatar(Attachment userAvatar) {
this.userAvatar = userAvatar;
}
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
#JsonProperty(access = Access.WRITE_ONLY)
private String temporaryPassword;
#Convert(converter = StringArrayConverter.class)
private String[] roles;
private Date lastPasswordReset;
private Date dateCreated;
private Date dateUpdated;
private Date validatyTime;
private Boolean active;
public User() {
lastPasswordReset = dateCreated = dateUpdated = new Date();
roles = new String[]{"USER"};
}
That is, when requesting with the support role, a list of users should be returned.
Spring-Security provides this support by just adding #PreAuthorize annotation
#RequestMapping(value = "getAll", method = RequestMethod.GET)
**#PreAuthorize("hasRole('ROLE_SUPPORT')")**
public List<User> getUsers() throws IOException {
return userService.getUsers();
}

Can't save many-to-many relations by form in JSP

The Context
I have a simple workshop application with three entities - Job, Employee and Customer. I am trying to create web interface which will add new Job in this case. Job has many to many relations with Employee and Customer. In Job entity there are lists of Employee and Customer as well.
The Problem
When I try to post my request with new Job through HTTP I get Bad Request 400 with description:
The server cannot or will not process the request due to something
that is perceived to be a client error (e.g., malformed request
syntax, invalid request message framing, or deceptive request
routing).
I don't know where excactly is bug.
Forms for adding Customer and Employee work fine.
The Code
Entities:
Employee
#Component
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Pattern (regexp="[a-zA-Z]+")
#NotEmpty
private String employeeName;
#Pattern (regexp="[a-zA-Z]+")
#NotEmpty
private String employeeSurname;
#ManyToMany(mappedBy = "employeeList")
private List<Job> jobList;
public Employee() {
}
public Employee(int id, String employeeName, String employeeSurname) {
this.id = id;
this.employeeName = employeeName;
this.employeeSurname = employeeSurname;
}
public Employee(String employeeName, String employeeSurname) {
this.employeeName = employeeName;
this.employeeSurname = employeeSurname;
}
public List<Job> getJobList() {
return jobList;
}
public void setJobList(List<Job> jobList) {
this.jobList = jobList;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getEmployeeSurname() {
return employeeSurname;
}
public void setEmployeeSurname(String employeeSurname) {
this.employeeSurname = employeeSurname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}}
Customer
#Component
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Pattern(regexp="[a-zA-Z]+")
#NotEmpty
private String CustomerName;
#Pattern (regexp="[a-zA-Z]+")
#NotEmpty
private String CustomerSurname;
#Pattern (regexp = "\\w+")
#NotEmpty
private String car;
private int phonenumber;
#ManyToMany(mappedBy = "customerList")
private List<Job> jobList;
public Customer(String customerName, String customerSurname, String car, int phonenumber) {
CustomerName = customerName;
CustomerSurname = customerSurname;
this.car = car;
this.phonenumber = phonenumber;
}
public Customer(int id, String customerName, String customerSurname, String car, int phonenumber) {
this.id=id;
CustomerName = customerName;
CustomerSurname = customerSurname;
this.car = car;
this.phonenumber = phonenumber;
}
public Customer() {
}
public List<Job> getJobList() {
return jobList;
}
public void setJobList(List<Job> jobList) {
this.jobList = jobList;
}
public String getCustomerName() {
return CustomerName;
}
public void setCustomerName(String customerName) {
CustomerName = customerName;
}
public String getCustomerSurname() {
return CustomerSurname;
}
public void setCustomerSurname(String customerSurname) {
CustomerSurname = customerSurname;
}
public int getPhonenumber() {
return phonenumber;
}
public void setPhonenumber(int phonenumber) {
this.phonenumber = phonenumber;
}
public String getCar() {
return car;
}
public void setCar(String car) {
this.car = car;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}}
Job
#Component
#Entity
public class Job {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotEmpty
#Pattern(regexp="[a-zA-Z]+")
private String jobName;
#ManyToMany(fetch = FetchType.EAGER)
private List<Employee> employeeList;
#LazyCollection(LazyCollectionOption.FALSE)
#ManyToMany
private List<Customer> customerList;
public Job() {
}
public Job(String jobName, List<Employee> employeeList, List<Customer> customerList) {
this.jobName = jobName;
this.employeeList = employeeList;
this.customerList = customerList;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public List<Employee> getEmployeeList() {
return employeeList;
}
public void setEmployeeList(List<Employee> employeeList) {
this.employeeList = employeeList;
}
public List<Customer> getCustomerList() {
return customerList;
}
public void setCustomerList(List<Customer> customerList) {
this.customerList = customerList;
}}
DaoImpl
JobDaoImpl
#Repository
public class JobDaoImpl implements JobDao {
#PersistenceContext
EntityManager entityManager;
#Override
public List<Job> findAllJobs() {
return entityManager.createQuery("select j from Job j order by j.id", Job.class)
.getResultList();
}
#Override
public Job addJob(Job job) {
entityManager.persist(job);
entityManager.flush();
entityManager.refresh(job);
return job;
}}
Service
JobService
#Service
#Transactional
public class JobService {
private JobDao jobDao;
public List<Job> findAllJobs(){
return jobDao.findAllJobs();
}
public Job addNewJob(Job job){return jobDao.addJob(job);}
public JobService(JobDao jobDao) {
this.jobDao = jobDao;
}
}
Controller
JobController
#Controller
public class JobController {
JobService jobService;
EmployeeService employeeService;
CustomerService customerService;
public JobController(JobService jobService, EmployeeService employeeService, CustomerService customerService) {
this.jobService = jobService;
this.employeeService = employeeService;
this.customerService = customerService;
}
//JOB INDEX
#RequestMapping("job-index.html")
public ModelAndView getJobIndex() {
ModelAndView modelAndView = new ModelAndView("jobViews/jobIndex");
return modelAndView;
}
//SHOW EMPLOYEES
#RequestMapping("show-jobs.html")
public ModelAndView getAllJobs() {
ModelAndView modelAndView = new ModelAndView("jobViews/jobs");
modelAndView.addObject("jobs", jobService.findAllJobs());
return modelAndView;
}
//ADD NEW JOB GET METHOD
#GetMapping(value = "add-job.html")
public ModelAndView addNewJob(){
return new ModelAndView("jobViews/addJob","job", new Job());
}
//ADD NEW JOB POST METHOD
#PostMapping(value = "add-job.html")
public ModelAndView addNewJob(#ModelAttribute Job job){
return new ModelAndView("jobViews/addJobConfirmation","job",job);
}
#ModelAttribute("employeeInit")
public List<Employee> initializeEmployees() {
return employeeService.findAllEmployee();
}
#ModelAttribute("customerInit")
public List<Customer> initializeCustomer(){ return customerService.findAllCustomer();}}
JSP Views
addJob.jsp
<f:form method="post" modelAttribute="job">
<p>Job name:<f:input path="jobName"/></p>
<f:hidden path="id"/>
<f:select path="employeeList" multiple="true">
<f:options items="${employeeInit}" itemLabel="employeeSurname" itemValue="id"></f:options>
</f:select>
<f:select path="customerList" multiple="true">
<f:options items="${customerInit}" itemLabel="customerSurname" itemValue="id"></f:options>
</f:select>
<button type="submit">Add</button>

Empty field Data Type in springfox swagger ui

I have a Spring Web MVC application with rest services and I try to use Springfox for automated docs for it.
I use
io.springfox:springfox-swagger2:2.5.0
io.springfox:springfox-swagger-ui:2.5.0
org.webjars:swagger-ui:2.1.4
My SwaggerConfig:
#Configuration
#EnableSwagger2
#Profile("dev")
public class SwaggerConfig {
#Bean
public Docket api() {
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build().apiInfo(apiInfo());
return docket;
}
}
And:
#Configuration
#EnableWebMvc
#Profile("dev")
public class SwaggerWebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
I have a rest controller:
#Api
#RestController
#RequestMapping(RegistrationRestController.ROOT_PATH)
public class RegistrationRestController {
private final static Logger log = LogManager.getLogger(RegistrationRestController.class);
public static final String ROOT_PATH = "/rest/registration";
private final ResponseUtil responseUtil;
private final UserService userService;
#Autowired
public RegistrationRestController(ResponseUtil responseUtil, UserService userService) {
log.debug("Instantiate RegistrationRestController bean");
this.responseUtil = Objects.requireNonNull(responseUtil);
this.userService = Objects.requireNonNull(userService);
}
#RequestMapping(value = {""}, method = RequestMethod.POST)
#ApiResponses({
#ApiResponse(code = 200, message = "Create new user and authorize them. Returns access token", response = AccessTokenDTO.class)
})
#ApiOperation(value = "Create new user", response = AccessTokenDTO.class)
public ResponseDTO newUserRegistration(#Valid #ApiParam(required = true) RegistrationDTO registrationDTO) {
log.debug("POST {} with registrationDTO='{}'", ROOT_PATH, registrationDTO.toString());
AccessTokenDTO accessToken = userService.createUserAndAuthorize(registrationDTO);
return responseUtil.wrapResult(accessToken);
}
}
And RegistrationDTO:
#ApiModel
public class RegistrationDTO {
#NotNull(message = "registrationDTO.phone.notNull.fail")
#Pattern(regexp = "[0-9]{10}", message = "registrationDTO.phone.pattern.fail")
private String phone;
#NotNull(message = "registrationDTO.email.notNull.fail")
#Email(message = "registrationDTO.email.pattern.fail")
private String email;
#NotNull(message = "registrationDTO.lastName.notNull.fail")
#Size(min = 1, message = "registrationDTO.lastName.size.fail")
private String lastName;
#NotNull(message = "registrationDTO.firstName.notNull.fail")
#Size(min = 1, message = "registrationDTO.firstName.size.fail")
private String firstName;
#NotNull(message = "registrationDTO.password.notNull.fail")
#Size(min = 6, message = "registrationDTO.password.size.fail")
private String password;
#NotNull(message = "registrationDTO.passwordConfirmation.notNull.fail")
#Size(min = 6, message = "registrationDTO.passwordConfirmation.size.fail")
private String passwordConfirmation;
#AssertTrue(message = "registrationDTO.isPasswordMatch.fail")
public boolean isPasswordMatch() {
return Objects.equals(password, passwordConfirmation);
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPasswordConfirmation() {
return passwordConfirmation;
}
public void setPasswordConfirmation(String passwordConfirmation) {
this.passwordConfirmation = passwordConfirmation;
}
#Override
public String toString() {
return "RegistrationDTO{" +
"phone='" + phone + '\'' +
", email='" + email + '\'' +
", lastName='" + lastName + '\'' +
", firstName='" + firstName + '\'' +
'}';
}
}
But when I'm opening localhost:8080/swagger-ui.html I see an empty Data type for RegistrationDTO parameter.
What I'm doing wrong? :(
Need to specify #RequestBody before method parameter

Enhanced Spring Data Rest delivers empty relations

in my current implementation using Spring-Boot, -HATEOAS, -Rest-Data I'm trying to spare some further rest calls and enhance my rest resource for credits to also deliver relations of a credit (see below account as ManyToOne and creditBookingClassPayments as OneToMany).
The problem now is that I'm not able to get it run. The call always delivers empty relations. I really would appreciate some help on this.
Here are the surroundings:
Credit.java
#Entity
#Getter
#Setter
public class Credit {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(NONE)
#Column(name = "id")
private Long itemId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="account_id", nullable = false)
private Account account;
#OneToMany(mappedBy = "credit")
private List<CreditBookingClassPayment> creditBookingClassPayments = new ArrayList<>();
#NotNull(message="Please enter a valid short name.")
#Column(length = 10, nullable = false)
private String shortName;
#NotNull(message="Please enter a valid name.")
#Column(nullable = false)
private String name;
...
}
CreditRepositoryCustomImpl.java
uses QueryDsl to enhance the credit resource with its realation
...
#Override
public List<Credit> findDistinctByAccountItemIdNew(Long accountId) {
QCredit credit = QCredit.credit;
QAccount account = QAccount.account;
QCreditBookingClassPayment creditBookingClassPayment = QCreditBookingClassPayment.creditBookingClassPayment;
QBookingClass bookingClass = QBookingClass.bookingClass;
BooleanExpression hasAccountItemId = credit.account.itemId.eq(accountId);
List<Credit> credits = from(credit).where(hasAccountItemId)
.innerJoin(credit.account, account)
.leftJoin(credit.creditBookingClassPayments, creditBookingClassPayment)
.leftJoin(creditBookingClassPayment.bookingClass, bookingClass).groupBy(credit.itemId).fetch();
return credits;
}
...
CreditController.java
looking into responseBody here all (account and credit payments) is available for credits
#RepositoryRestController
public class CreditController {
#Autowired
private CreditRepository creditRepository;
#RequestMapping(value = "/credit/search/findAllByAccountItemIdNew", method= RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resources<PersistentEntityResource>> findAllByAccountItemIdNew(#RequestParam Long accountId, PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
List<Credit> credits = creditRepository.findDistinctByAccountItemIdNew(accountId);
Resources<PersistentEntityResource> responseBody = new Resources<PersistentEntityResource>(credits.stream()
.map(persistentEntityResourceAssembler::toResource)
.collect(Collectors.toList()));
return ResponseEntity.ok(responseBody);
}
}
CreditResourceIntegrTest.java
here creditResourcesEntity hold the credit but account is null and creditBookingClassPayment is an empty array
#Test
public void testFindAllByAccountItemId() throws URISyntaxException {
URIBuilder builder = new URIBuilder(creditFindAllByAccountItemIdRestUrl);
builder.addParameter("accountId", String.valueOf(EXPECTED_ACCOUNT_ID));
builder.addParameter("projection", "base");
RequestEntity<Void> request = RequestEntity.get(builder.build())
.accept(MediaTypes.HAL_JSON).acceptCharset(Charset.forName("UTF-8")).build();
ResponseEntity<Resources<Resource<Credit>>> creditResourcesEntity =
restTemplate.exchange(request, new ParameterizedTypeReference<Resources<Resource<Credit>>>() {});
assertEquals(HttpStatus.OK, creditResourcesEntity.getStatusCode());
//assertEquals(EXPECTED_CREDIT_COUNT, creditResourcesEntity.getBody().getContent().size());
}
Do I miss something?
Thanks for your help!
Karsten
Okay, PersistentEntityResourceAssembler doesn't support relations. But this could be handled by using projections.
CreditProjection.java
#Projection(name = "base" , types = Credit.class)
public interface CreditProjection {
String getShortName();
String getName();
List<CreditBookingClassPaymentProjection> getCreditBookingClassPayments();
BigDecimal getValue();
BigDecimal getInterestRate();
BigDecimal getMonthlyRate();
}
CreditBookingClassPaymentProjection.java
#Projection(name = "base" , types = CreditBookingClassPayment.class)
public interface CreditBookingClassPaymentProjection {
BookingClass getBookingClass();
CreditPaymentType getCreditPaymentType();
}
CreditController.java
#RepositoryRestController
public class CreditController {
#Autowired
private ProjectionFactory projectionFactory;
#Autowired
private CreditRepository creditRepository;
#RequestMapping(value = "/credit/search/findAllByAccountItemIdNew", method = RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resources<?>> findAllByAccountItemIdNew(#RequestParam Long accountId,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
List<Credit> credits = creditRepository.findDistinctByAccountItemIdNew(accountId);
List<PersistentEntityResource> creditResources = new ArrayList<>();
for (Credit credit : credits) {
// credit.getCreditBookingClassPayments()
PersistentEntityResource creditResource = persistentEntityResourceAssembler.toResource(credit);
creditResources.add(creditResource);
}
Resources<CreditProjection> responseBody = new Resources<CreditProjection>(credits.stream()
.map(credit -> projectionFactory.createProjection(CreditProjection.class, credit))
.collect(Collectors.toList()));
return ResponseEntity.ok(responseBody);
}
}

Resources