#PreAuthorize how do i add a parameter into the expression - spring

Maybe somebody has an idea
I have an abstract controller providing me the endpoints i need.
In the #PreAuthorized i would execute the check if the user has the required roles.
Problem is that i have only one function and i want to check which endpoint is currently evaluated.
This is the code:
public abstract class CoreController<T> {
private final JpaRepository repository;
private final CoreService service;
public String endpoint;
private String view;
public CoreController(CoreService service, JpaRepository repository, String endpoint, String view) {
this.service=service;
this.repository = repository;
this.endpoint=endpoint;
this.view=view;
}
#PreAuthorize("#checkAccess.isAllowedToGet(#endpoint)")
#RequestMapping(method = RequestMethod.GET, value = "/get")
public ResponseEntity<CrudPage<Map<String, Object>>> get(CoreCriteria criteria) {
criteria.setView(view);
CrudPage<Map<String, Object>> data = service.getPage(criteria);
return ResponseEntity.ok(data);
}
The problem is the endpoint is always null when the function is called.
How should i change the expression to make this work.

Related

Mocked JPA repository bean is not returning assigned value from a mocked method call

I have a interface CalculatorService whose implementation contains business logic.
Interface:
public interface CalculatorService {
LoanWeb calculateSimpleLoan(LoanWeb loanWeb) throws Exception;
}
Implementation:
#Service
#AllArgsConstructor
public class CalculatorServiceImpl implements CalculatorService {
private final LoanRepository loanRepository; //this extends JpaRepository
private final PropertiesConfig propertiesConfig;
private final CalculatorUtility calculatorUtility;
private final LoanInfoRepository loanInfoRepository;
#Override
public LoanWeb calculateSimpleLoan(LoanWeb loanWeb) throws Exception {
//validation
if (loanWeb.getLoanTerm() == null || loanWeb.getLoanTerm() <= 0) throw new LeanPayException(ErrorCode.INVALID_INTEGER_ERROR.code, List.of("loanTerm"));
var payments = someCalculation();
Loan loan = loanRepository.findFirstByAmountAndRateAndPaymentsAndFrequency
(loanWeb.getLoanAmount(), loanWeb.getInterestRate(), payments, Frequency.MONTHLY); //loan is null???!!!
if (loan != null) return LoanWeb.builder() //this is false, I need it to be true!
.monthlyPayment(loan.getPmt())
.totalInterestPaid(loan.getLoanInfos().stream().mapToDouble(LoanInfo::getInterest).sum()).build();
}
}
JpaRepository:
#Repository
public interface LoanRepository extends JpaRepository<Loan, Long> {
Loan findFirstByAmountAndRateAndPaymentsAndFrequency(Double amount, Double rate, Integer payments, Frequency frequency);
}
Test class:
It looks like this:
#SpringBootTest(properties = {"spring.cloud.config.enabled: false", "logging.level.com.package.calculator: OFF"},
classes= {ObjectMapper.class, CalculatorServiceImpl.class, LoanRepository.class})
public class CalculatorServiceTest {
#Autowired private ObjectMapper objectMapper;
#Autowired
private CalculatorService calculatorService;
#MockBean(classes = LoanRepository.class) private LoanRepository loanRepository;
#MockBean private PropertiesConfig propertiesConfig;
#MockBean private CalculatorUtility calculatorUtility;
#MockBean private LoanInfoRepository loanInfoRepository;
#MockBean private TestService testService;
private Loan loan1; //this is not null, it is correctly loaded from a file.
#PostConstruct
public void init() throws IOException{
String jsonString =
IOUtils.toString(
Objects.requireNonNull(this.getClass().getResourceAsStream("/json/PreExistingLoan.json")), StandardCharsets.UTF_8);
this.loan1 = objectMapper.readValue(jsonString, Loan.class);
}
#Test
void calculateSimpleLoanWithPreExistingLoan() throws LeanPayException {
Mockito.doReturn(this.loan1).when(loanRepository).findFirstByAmountAndRateAndPaymentsAndFrequency( ArgumentMatchers.anyDouble(), ArgumentMatchers.anyDouble(),
ArgumentMatchers.anyInt(), ArgumentMatchers.any(Frequency.class));
LoanWeb result = this.calculatorService.calculateSimpleLoan(LoanWeb.builder().loanTerm(10).simpleLoanTerm(SimpleLoanTerm.MONTH).build());
Assertions.assertEquals(result.getTotalInterestPaid(), 10);
Assertions.assertEquals(result.getMonthlyPayment(), 107.0);
}
}
Application context is brought up, test runs, but mocked repo method call returns null in service. Where am I wrong? I tried million things, this is code from beginning, don't have any ideas.
ArgumentMatchers.anyDouble() matches any double (primitive) or non-null Double (boxed) value.
If you happen to pass a null value, it won't be matched.
You have a couple of options:
use ArgumentMatchers.any() which matches anything, including nulls
use ArgumentMatchers.eq(null) on the argument where the null is passed
modify your test to pass a non-null value

Spring TransactionManager behavior with Spring Data and JpaRepository

I have a controller which does the following
A submit end point which save an entry in db and then call some external service asynchronously
Track the update of asynchronous call (this call updates an associated table) by watching the db and update the status of the entry created in step one
I was using the #Query Annotation to verify if step one entry exist in db and it was always returning empty. I tried changing it to the default spring method and it starts returning the inserted value.
I read about proxies, #Transactional and how non CRUD methods in a JPARepository are non transactional and tried few things like transaction propagation and self injection and even explicitly marking the repo method #Transactional. But none of them fixed the issue. Using spring data method solved it but I still don't understand what happened. Can someone help with an explanation of this behavior.
Basic code snippet is below
MyController
#RestController
public class MyController {
private final MyService myService;
private final MyRepository myRepository;
#Autowired
public MyController(MyService myService,
MyRepository myRepository) {
this.myService = myService;
this.myRepository = myRepository;
}
#PostMapping(value = "/submit")
public ResponseEntity<MyResponse> submit(#Valid #RequestBody MyRequest myRequest) {
return ResponseEntity
.accepted()
.body(MyResponse.success(myService.submit(myRequest), "SUBMITTED"));
}
/**
* This method is to update the status of the entry created by /submit endpoint
* if the asynchoronous process triggered by submit endpoint update an associated table
*/
#PostConstruct
private void trackUpdates() {
..
someObserver.subscribe(trackedAssociatedEntity -> {
myService.trackAndUpdateBasedOnAssociatedEntity(trackedAssociatedEntity);
});
}
}
MyService
#Service
#Transactional
public class MyService {
private final MyRepository myRepository;
#Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
submit(MyRequest myRequest) {
myRepository.save(myEntity);
//makes that asynchronous call
}
public void trackAndUpdateBasedOnAssociatedEntity(#NotNull MyAssociatedEntity myassociatedEntity) {
// This commented call always return empty but the uncommented code works as expected
// List<MyEntity> existingEntity =
// myRepository.findEntityByField1AndField2(myassociatedEntity.getField1(),myassociatedEntity.getField2());
List<MyEntity> existingEntities =
myRepository.findByField1AndField2(myassociatedEntity.getField1(),myassociatedEntity.getField2());
if(existingEntities.isEmpty()){
//create new
}else{
//update
}
}
}
}
}
MyRepository
#Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT e FROM MyEntity e WHERE e.field1 = ':field1' and e.field2 = ':field2' ")
List<MyEntity> findEntityByField1AndField2(String field1, String field2);
List<MyEntity> findByField1AndField2(String field1, String field2);
}
I believe that '' are not needed. Please try the following:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT e FROM MyEntity e WHERE e.field1 = :field1 and e.field2 = :field2")
List<MyEntity> findEntityByField1AndField2(String field1, String field2);
List<MyEntity> findByField1AndField2(String field1, String field2);
}

how to construct #service in Springboots using a payload variable

I'm quite new in spring boots so I hope this is not a silly question
I have a #Service that needs to initiate a class attribute, this attribute needs a information that comes from the RestPayload in the Controller. I'm not finding the most recommend way to do that.
#RestController
public class UserController {
#Autowired
private UserService userService;
#RequestMapping("/searchUser")
public List<UserWrapper> searchUser(#RequestBody UserWrapper userWrapper) {
List<UserWrapper> returnUserWrapper = userService.findByName(userWrapper);
return returnUserWrapper;
}
}
And the service layer, I would like to be something like:
#Service
public class UserService {
private LdapTemplate ldapTemplate;
public static final String BASE_DN = "xxxxxxx";
#Value( value = "${sample.ldap.url}" )
private String ldapUrl;
#Value( value = "${sample.ldap.base}" )
private String ldapBase;
public UserService() {
}
public UserService(String dn, String password) {
LdapContextSource ctxSrc = new LdapContextSource();
System.out.println(this.ldapUrl);
ctxSrc.setUrl(ldapUrl);
ctxSrc.setBase(ldapBase);
ctxSrc.setUserDn(dn);
ctxSrc.setPassword(password);
ctxSrc.afterPropertiesSet(); // this method should be called.\
this.ldapTemplate = new LdapTemplate(ctxSrc);
}
The String dn and String password will come in the REST Payload but the other properties comes from a properties file.
Hope someone can guide me with best practices
for ldap authentication you should have a look on spring-security:
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#ldap
on the other hand you can access almost any request parameter by just injecting it via a annotation like in these examples:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-requestheader

How to send Java collections containing subclasses to spring controller

I'm trying to send collections to my spring MVC controller:
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#ModelAttribute("mapperList") MapperList mapperList) {
//prints {"users":null}
System.out.println(new ObjectMapper().writeValueAsString(mapperList));
return new ResponseDTO();
}
this is the code posting my users :
public ResponseDTO postUsers(ArrayList<User> users) {
ResponseDTO serverResponse = null;
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestMethod("POST");
// prints {"users":[{"property1":"x","property1":y}]}
System.out.println(objectMapper.writeValueAsString(new MapperList(users)));
objectMapper.writeValue(connection.getOutputStream(), objectMapper.writeValueAsString(new MapperList(users)));
//blabla ...
}
and this is the object containing my list :
public class MapperList implements Serializable {
private static final long serialVersionUID = 8561295813487706798L;
private ArrayList<User> users;
public MapperList() {}
public MapperList(ArrayList<User> users) {
this.setUsers(users);
}
public ArrayList<User> getUsers() {
return users;
}
public void setUsers(ArrayList<User> users) {
this.users = users;
}
}
and this is the users type to post:
public abstract class User implements Serializable {
private static final long serialVersionUID = -1811485256250922102L;
private String property1;
private String property2;
public User() {}
public User(String prop1, String prop2) {
// set properties
}
// getters and setters
}
the problem is, when I output the value of the users's array before to post it to the controller, I got the following json value :
{"users":[{"property1":"x","property1":y}]}
but in the controller, when I print what I get from the request body, I only get :
{"users":null}
I also tryed with the annotation #RequestBody instead of #ModelAttribute("mapperList") and a JSONException is displayed :
*A JSONObject text must begin with '{' at 1 [character 2 line 1]\r\n*
My array list of users contains only one user that should be displayed. I don't understand why this doesn't work...
Thanks for any help !
You can chnage your MapperList class definition as public class MapperList extends ArrayList<User>{ ..} you dont need to define any instance variable like private ArrayList users inside MapperList class. Use #Requestbody annotation. You will be able to use MapperList as a ArrayList
Try to use:
public class MapperList{
private List<User> users;
//setter and getter
//toString
}
public class User{
private String property1;
private String property2;
//getter + setter
}
json:
{"users":[{"property1":"x", "property2":"y"}]}
in controller use #RequestBody. In that case Jackson will map your json to ArrayList of users.
#ResponseStatus(HttpStatus.OK)
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList users) {
System.out.println(users);
return null;
}
no need to get objectMapper in that case. Don't forget to set content-type in request header to application/json. It required by Spring to handle #RequestBody processing.
If not working try to change MapperList:
List<User> users = new ArrayList<User>();
On the server side keep the #RequestBody annotation:
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList mapperList)
...
But this line causes problems:
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
First it converts the object to JSON and then again uses objectMapper to JSON-encode the string into output stream. Try the following instead:
connection.getOutputStream().write(
objectMapper.writeValueAsString(new MapperList(users))
.getBytes("UTF-8")
);
or directly output to stream:
objectMapper.writeValue(
connection.getOutputStream(),
new MapperList(users))
);
Zbynek gave me part of the answer. Indeed
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
doesn't work properly in my case
But moreover, my User class was an abstract class, with many type of User as subclasses. so the #RequestBody annotation couldn't work without specified the object type in the Json.
I used the following annotations on User class to make it working :
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SubClassA.class, name = "a"),
#JsonSubTypes.Type(value = SubClassB.class, name = "b")
})
Thanks a lot for all your answers.

Spring Data REST custom query integration

I want to create a REST link for an Employee entity that will basically be a findByAllFields query. Of course this should be combined with Page and Sort. In order to do that I have implemented the following code:
#Entity
public class Employee extends Persistable<Long> {
#Column
private String firstName;
#Column
private String lastName;
#Column
private String age;
#Column
#Temporal(TemporalType.TIMESTAMP)
private Date hiringDate;
}
So I would like to have lets say a query where I can do:
http://localhost:8080/myApp/employees/search/all?firstName=me&lastName=self&ageFrom=20&ageTo=30&hiringDateFrom=12234433235
So I have the following Repository
#RepositoryRestResource(collectionResourceRel="employees", path="employees")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long>,
JpaSpecificationExecutor<Employee> {
}
Ok so now I need a RestController
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#RequestMapping(value = "/employees/search/all/search/all", method = RequestMethod.GET)
public Page<Employee> getEmployees(EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
return employeeRepository.findAll(specification, pageable);
}
Ok, obviously this does its job but it is not integrated with HATEOAS.
I have attempted to assemble a resource changing the controller to this:
public PagedResources<Resource<Employee>> getEmployees(
PagedResourcesAssembler<Employee> assembler,
EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return assembler.toResource(employees);
}
Obviously I'm missing something from the above since it doesnt work and I'm getting the following Exception:
Could not instantiate bean class [org.springframework.data.web.PagedResourcesAssembler]: No default constructor found;
Ok so to make the question clear I am trying to integrate the above resource into the rest of the HATEOAS architecture. I'm not entirely sure if this is the correct approach so any other suggestions are welcome.
EDIT:
Here you can see a similar implementation. Please take a look at the configuration, you will see that all but one of the "Person" controllers are working.
https://github.com/cgeo7/spring-rest-example
Try autowring PagedResourcesAssembler as a class member and change method signature something like below
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#Autowired
private PagedResourcesAssembler<Employee> pagedAssembler;
#RequestMapping(value = "/employees/search/all/search/all", method = RequestMethod.GET)
public ResponseEntity<Resources<Resource<Employee>>> getEmployees(EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return assembler.toResource(employees);
}
}
This works perfectly with Spring Data Rest 2.1.4.RELEASE
The code by #Stackee007 works but the resource won't include self links. In order to do that, a little more is required.
#Autowired
PagedResourcesAssembler<Appointment> pagedResourcesAssembler;
#RequestMapping(value = "/findTodaysSchedule")
public HttpEntity<PagedResources<Resource<Appointment>>> getTodaysSchedule(
PersistentEntityResourceAssembler entityAssembler, Pageable pageable) {
Page<Appointment> todaysSchedule = apptRepo.findByStartTimeBetween(beginningOfDay, endOfDay, pageable);
#SuppressWarnings({ "unchecked", "rawtypes" })
PagedResources<Resource<Appointment>> resource = pagedResourcesAssembler.toResource(todaysSchedule,
(ResourceAssembler) entityAssembler);
return new ResponseEntity<>(resource, HttpStatus.OK);
}
Spring HATEOAS has changed the name of Resource, PagedResources and some other classes. See here. Below is a working version in 2020.
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#Autowired
private PagedResourcesAssembler<Employee> pagedAssembler;
#RequestMapping(value = "/employees/search/all", method = RequestMethod.GET)
public ResponseEntity<PagedModel<EntityModel<Employee>>> getEmployees(PersistentEntityResourceAssembler entityAssembler,,
EmployeeCriteria filterCriteria,
Pageable pageable) {
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return ResponseEntity.ok(pagedAssembler.toModel(plants, (RepresentationModelAssembler) entityAssembler));
}
}

Resources