Spring REST projection does not kick in - spring

I have a domain with uml diagram
here.
I have declared JpaRepositoryes for classes: Invoice, Contract, Consultant, User, Region
I don't want to expose the repositories as they are because I will need to wrap some business rules around them.
I have defined a few projections and they seem to work with my repositories yet they do not kick in with my #RestController
Here is a draft of my wrapper:
#RestController
#RequestMapping("/contractService")
public class ContractServiceImpl extends ServiceImpl<Contract, Long> implements IService<Contract, Long>
{
#RequestMapping("/all")
public List<Contract> all()
{
return findAll();
}
#RequestMapping(value = "/one", method = GET)
public Contract one(#RequestParam(value = "id", defaultValue = "1") Long id)
{
log.info(format("id=%d", id));
return repository.getOne(id);
}
#Autowired
RepositoryRestConfiguration rrc;
#RequestMapping("config")
public List<String> getConfig()
{
return rrc.projectionConfiguration()
.getProjectionsFor(Contract.class)
.entrySet().stream()
.map(e -> e.getKey() + "->" + e.getValue())
.collect(Collectors.toList());
}
Here the last method is to verify if projections got configured. i.e.:
http://localhost:8080/invoiceapi/contractService/config
gets me the result as:
[
"brief->interface e.invoice.entity.projection.Brief",
"contractEdit->interface e.invoice.entity.projection.ContractEdit"
]
My projection is defined as:
#Projection(name="brief", types={Contract.class, Consultant.class, User.class, Region.class})
public interface Brief
{
public Long getId();
public String getName();
}
Rest calls into repository directly returns me the projected results as desired, id and name only:
http://localhost:8080/invoiceapi/contracts?projection=brief
http://localhost:8080/invoiceapi/contracts/1?projection=brief
However those into my controller:
http://localhost:8080/invoiceapi/contractService/all?projection=brief
http://localhost:8080/invoiceapi/contractService/one?id=2&projection=brief
they recursively spider all reachable entities as far as the defined JpaRepositories go. (The Application objects below User do not show up)
My controller returns a document with Content-Type: application/json while JpaRepository based one returns a cool looking Content-Type: application/hal+json. Their output are different as well: My controller returns a more straightforward output while JpaRepository based one puts associated objects into an array called _embedded.

Related

REST with Spring: Where to place logic for mapping from frontend DTO to JPA Specification and Pageable?

I want to use a rather complex DTO (from the frontend) which contains filtering and pagination parameters to map/build a Specification and a Pageable to use in query in the underlying JPA Repository. The call stack is as follows: controller -> view -> service -> repository.
What would be conceptually the cleanest approach to place the DTO-to-Specification/Pageable method(s)?
For comparison: so far, I placed it as a private method in the service class, like so (not an actual code snippet). Is that architecturally clean or would e.g. a separate class (like a FooSpecificationBuilder) be more appropriate?
#Service
public class FooService {
private final FooRepository repository;
public Page<Foo> getFooPage(FrontendSearchDto searchDto) {
Specification<Foo> spec = buildSpec(searchDto);
Pageable pageable = buildPageable(searchDto);
return repository.findAll(spec, pageable);
}
private Specification<Foo> buildSpec(FrontendSearchDto searchDto) {
// do the mapping
}
private Pageable buildPageable(FrontendSearchDto searchDto) {
// do the mapping
}
}

#RepositoryRestResource changes url every time the application is restarted

I have a repository interface that extends JpaRepository and a NameRepositoryCustom.
My repository is annotated with #RepositoryRestRessource(collectionResourceRel="pathname", path="pathname").
The problem I have is that every second restart of my application the URL of the repository gets changed so I can't find the exposed data of the repository under the URL I defined and some features like the search of the repository aren't exposed in the API anymore either.
The "NameRepositroyCustom" is used for a search function which uses another Repository to implement Specification with JPA Criteria Api for a searchbar in my frontend.
Does anybody have a solution for this? The only repository annotated as #RepositoryRestRessource is the main repository that implements all the others. The NameRepositorySpec is annotated with #Repository, could this maybe be the cause?
Edit: I implemented the code as an example to clarify the relations between the mentioned classes and interfaces.
This is the basic repository related to the entity persisted in the database:
#RepositoryRestResource(collectionRessourceRel = "enitynames", path = "entitynames")
public interface EntitynameRepository extends JpaRepository<Entityname, Long>, EntitynameRepositoryCustom{
//custom methods in here
}
This is the custom repository:
public interface EntitynameRepositoryCustom {
Page<Entityname> search(String exampleParam1, String exampleParam2, Pageable pageable);
}
This is the implementation of the custom repository:
public class EntitynameRepositoryCustomImpl implements EntitynameRepositoryCustom{
#Autowired
EntityManager em;
#Autowired
EntitynameRepositorySpec entitynameRepositorySpec;
Specification<Entityname> querySpecification = null;
#Override
public Page<Entityname> search(String exampleParam1, String exampleParam2, Pageable pageable) {
//Code here uses the criteria builder and Specification to generate a custom query with optional parameters
CriteriaBuilder cb= em.getCriteriaBuilder();
CriteriaQuery<Entityname> cq = cb.createQuery(Entityname.class);
//Code below is done for every passed in parameter
if(exampleParam1 != null){
Specification<Entityname> param1Specification = EntitynameSpecification.likeParam1(exampleParam1);
querySpecification = Specification.where(param1Specification);
} else {
return null;
}
return entitynameRepositorySpec.findAll(specification, pageable);
}
}
This is the specification repository:
public interface EntitynameRepositorySpec extends JpaRepository<Entityname, Long>, JpaSpecificationExecutor<Entityname>{
}
And this is the implementation of the specification:
public class EntitynameSpecification {
public static Specification<Entityname> likeExampleParam1(String exampleParam1){
if(exampleParam1 == null){
return null;
}
return(root, query, cb) -> {
reutrn cb.like(root.get("fieldname"), "%"+ exampleParam1 + "%");
};
}
}
The URL of the repository gets changed to a part of the entity name compared to my example it would be something like: entityname has URL: /entityname
if the bug occurs the URL changes to /name.

Spring HATEOAS recursive representation model processors?

I have a question concerning the representation model processors of Spring HATEOAS. We are experimenting to process models before serializing them to the client. Our use case is to enrich the imageUrl field of UserModel objects at runtime, as we have to build the URL based on values from a config bean (AWS S3 bucket URL differs for DEV / PROD setup).
#Data
public class UserModel {
// ...
private String imageUrl;
}
Therefore, we create a UserProcessor to implement this:
public class UserProcessor implements RepresentationModelProcessor<EntityModel<UserModel>> {
private final ConfigAccessor configAccessor;
public UserProcessor(ConfigAccessor configAccessor) {
this.configAccessor = configAccessor;
}
#Override
public EntityModel<UserModel> process(EntityModel<UserModel> model) {
if (model.getContent() != null)
// do the enrichment and set "imageUrl" field
}
return model;
}
}
This works perfectly if we have a controller method like this:
#ResponseBody
#GetMapping("/me")
public EntityModel<UserModel> getCurrentUser(#AuthenticationPrincipal Principal principal) {
UserModel user = ... // get user model
return EntityModel.of(user);
}
However, we are struggling now with the enrichment whenever a UserModel is referenced in another model class, e.g., the BookModel:
#Data
public class BookModel {
private String isbn;
// ...
private EntityModel<UserModel> user; // or "private UserModel user;"
}
A controller method returning type EntityModel<BookModel> only applies the processor for its type, but not for types that are referenced. It seems the processors are not applied recursively.
Is this intentional or are we doing something wrong?
Thanks for any input and help,
Michael
I encountered the same issue and I resolved it by manually assembling resources, in your case that would be implementing RepresentationModelAssembler of the BookModel and then manually invoking the processor on the userModel object that is inside the book.
Make the outer resource a representation model
First consider the BookModel to extend RepresentationModel so that you can manually add links and assemble inner resources (which you would like for the EntityModel<UserModel> object)
#Data
public class BookModel extends RepresentationModel<BookModel> {...}
Write a model assembler
Now write the assembler that takes your book entity and transforms it into a representation model or a collection of these models. You will implement here what EntityModel.of(...) does for you automagically.
#Component
public class BookModelAssembler implements RepresentationModelAssembler<Book, BookModel> {
#Autowired
private UserProcessor userProcessor;
#Override
public BookModel toModel(Book entity) {
var bookModel = new BookModel(entity) // map fields from entity to model
// Transform the user entity to an entity model of user
var user = entity.getUser();
EntityModel<UserModel> userModel = EntityModel.of(user);
userModel = userProcessor.process(userModel);
bookModel.setUserModel(userModel);
return bookModel;
}
}
I might be going out on a limb but I suppose the reason for this is that the processors get invoked when an MVC endpoint returns a type that has a registered processor, which in the case of embedded types is not invoked. My reasoning is based on the docs for RepresentationModelProcessor, which states that processor processes representation models returned from Spring MVC controllers.

Querying mongodb collection SpringWebFlux with reactivemongodb

I am developing simple spring webflux demo application with reactive mongodb and i want to read all data of Employee by name except containing name field "joe","Sara","JOE","SARA" and i have following code like:
//repository interface
public interface EmployeeRepository extends ReactiveMongoRepository<Employee, String>{
Flux<Employee> findAllByName(String name);
}
//Service class
public class EmplyeeService
{
private EmployeeRepository employeeRepository;
public Flux<Employee> findAllByOrganizationName(String name)
{
return employeeRepository.findAllByName(name);
}
public Flux<String> getAllNameExceptSome(String name)
{
Employee emp1=new Employee();
List<Flux<Employee>> emp=Arrays.asList(employeeRepository.findAllByName(name));
Flux<Flux<Employee>> emp2=Flux.fromIterable(emp)
.filter(name->name.equalsIgnoreCase("joe"));
return emp2;
}
}
First of all, unless some particular situations, you should avoid these data structures:
List<Flux<Employee>>
Flux<Flux<Employee>>
However you are not leveraging Spring Data. You can achieve you result simply changing your repository to:
public interface EmployeeRepository extends ReactiveMongoRepository<Employee, String> {
// this find all Employee except those matching names provided as param
Flux<Employee> findAllByNameNotIn(List<String> nameList);
// this find all Employee matching names provided as param
Flux<Employee> findAllByNameIn(List<String> nameList);
}
Invoking this method you will obtain the list of Employee already filtered by name.

Spring DATA REST - How to convert entities to resources in custom controller using default spring implementation

I have created a custom controller which needs to convert entities to resources. I have annotated my repositories with #RepositoryRestResource annotation. I want to know if there is a way I can invoke the default functionality of spring Data REST from my custom controller which serializes the entities to resources with links to other entities embedded in them.
I don't want to return entities from my handler method but Resources.
Thanks.
Very simple, using objects Resource or Resources. For example - in this controller we add custom method which return list of all user roles which are enums:
#RepositoryRestController
#RequestMapping("/users/roles")
public class RoleController {
#GetMapping
public ResponseEntity<?> getAllRoles() {
List<Resource<User.Role>> content = new ArrayList<>();
content.addAll(Arrays.asList(
new Resource<>(User.Role.ROLE1),
new Resource<>(User.Role.ROLE2)));
return ResponseEntity.ok(new Resources<>(content));
}
}
To add links to resource you have to use object RepositoryEntityLinks, for example:
#RequiredArgsConstructor
#RepositoryRestController
#RequestMapping("/products")
public class ProductController {
#NonNull private final ProductRepo repo;
#NonNull private final RepositoryEntityLinks links;
#GetMapping("/{id}/dto")
public ResponseEntity<?> getDto(#PathVariable("id") Integer productId) {
ProductProjection dto = repo.getDto(productId);
return ResponseEntity.ok(toResource(dto));
}
private ResourceSupport toResource(ProductProjection projection) {
ProductDto dto = new ProductDto(projection.getProduct(), projection.getName());
Link productLink = links.linkForSingleResource(projection.getProduct()).withRel("product");
Link selfLink = links.linkForSingleResource(projection.getProduct()).slash("/dto").withSelfRel();
return new Resource<>(dto, productLink, selfLink);
}
}
For more example see my 'how-to' and sample project.

Resources