Spring Data REST custom API url for HATEOAS? - java

What I've known are:
If I define a custom method in the Repository interface, it will show in the URL like http://localhost/{repository}/search/{myMethod}. For example, I can define a recent method in the Order Repository interface (I can do this by using #Query simple and clean) and I can get the most recent object via http://localhost/order/search/recent.
If I use #RepositoryRestController to make a custom controller class, I have to implement HATEOAS using Resource and Resources myself and add a link. But I will get the right URL I want like http://localhost/{myPath}. For example, if I want to get the most recent objects of Order, I have to write codes below:
#RepositoryRestController
public class RecentOrdersController {
private OrderRepository orderRepo;
#Autowired
public RecentOrdersController(OrderRepository orderRepo) {
this.orderRepo = orderRepo;
}
#GetMapping(path = "/orders/recent", produces = "application/hal+json")
public ResponseEntity<Resources<OrderResource>> recentOrders() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<Order> orders = orderRepo.findAll(page).getContent();
List<OrderResource> orderResources =
new OrderResourceAssembler().toResources(orders);
Resources<OrderResource> recentResources =
new Resources<OrderResource>(tacoResources);
recentResources.add(
linkTo(methodOn(RecentOrdersController.class).recentOrders())
.withRel("recents"));
return new ResponseEntity<>(recentResources, HttpStatus.OK);
}
}
and then:
#Bean
public ResourceProcessor<PagedResources<Resource<Order>>>
orderProcessor(EntityLinks links) {
return new ResourceProcessor<PagedResources<Resource<Order>>>() {
#Override
public PagedResources<Resource<Order>> process(
PagedResources<Resource<Order>> resource) {
resource.add(
links.linkFor(Order.class)
.slash("recent")
.withRel("recents"));
return resource;
}
};
}
I have to write the OrderResource and OrderResourceAssembler myself and which make this even worse is that there may many other domains like Person in the Order domain, I have to wirte the xxxResource and xxxResourceAssembler myself too.
My question is how can I combine those?
For example, if I define a domain class named Order and enable the spring data REST, how can I get the most recent orders via URL like http://localhost/recent with fully HATEOAS support at minimal effort?

Related

How to create a dynamic proxy of a already proxyed class in spring

I'm relativly new to spring/spring boot.
At the moment I'm using a spring boot rest application which provides an FeignClient to be included in other projects. Now, I want those FeignClients be wrapped by a CircuitBreaker.
The best solution I came up with, is that I dynamically create a proxy which includes the CircuitBreaker implementation which itself calls the created FeignClient.
So let's assume I have the following interface which describes the RestController:
#RequestMapping("/")
public interface MyWebService {
#GetMapping("name")
public String getName();
}
Now, I have the interface for the FeignClient:
#FeignClient("app")
public interface WebServiceClient extends WebService {
}
So.. My goal would be to achieve something like I have another annotation e. g. #WithCircuitBreaker which I then will be scanned for and dynamically create a proxy bean which will be injected instead of the FeignClient bean.
At the moment my code looks like this:
#FeignClient("app")
#WithCircuitBreaker
public interface WebServiceClient extends WebService {
}
As far as I know, I can now create a #Configuration Class which will look like this:
#Configuration
public class WithCircuitBreakerConfiguration implements ImportAware {
private AnnotationMetadata annotationMetadata;
private AnnotationAttributes annotationAttributes;
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.annotationMetadata = importMetadata;
Map<String, Object> annotatedClasses = importMetadata.getAnnotationAttributes(WithCircuitBreaker.class.getName());
this.annotationAttributes = AnnotationAttributes.fromMap(annotatedClasses);
}
What else to import to create the proxy and inject it?
}
Now I'm at the point, which I don't know how to continue. How to dynamically create a proxy class which does something like this:
public class PorxyedWebService {
private WebService feignClientProxy;
#Autowired
public ProxyedWebService(WebService feignClientProxy) {
this.feignClientProxy = feignClientProxy;
}
public String getName() {
...
<some circuitbreaker stuff>
....
return this.feignClientProxy.getName();
}
}
and then return this proxy instead of the proxy generated from Feign as soon as someone autowires the WebService interface.
I am not a Spring user, but I do know that Spring does not create proxies recursively if e.g. multiple Spring AOP aspects are applied to the same object. Instead, additional interceptors (or advices in AOP language) are registered upon the same proxy. I think you want to use that infrastructure in order to achieve whatever your objective is.
You can just use the resilience4j Spring Boot2 starter.
You can combine the #CircuitBreaker annotation with the #FeignClient annotation at interface level.
You can then use it as follows:
#FeignClient(name = DUMMY_FEIGN_CLIENT_NAME)
#CircuitBreaker(name = DUMMY_FEIGN_CLIENT_NAME)
public interface DummyFeignClient {
String DUMMY_FEIGN_CLIENT_NAME = "dummyFeignClient";
#GetMapping(path = "/api/{param}")
void doSomething(#PathVariable(name = "param") String param);
}

Child controller in spring boot

Let's imagine a scenario where I have a Spring Boot app (in a controller/service/repository pattern) which contains controller for cars. Now I would like to create paths that look for example like this:
"/api/cars/bmw"
"/api/cars/mercedes"
"/api/cars/audi"
And so on. And for each of these car producers I would like to have multiple endpoints, some of them common for all car producers (not sure if it really matters but just for the sake of it lets say for example "/order" and "/cancelOrder"), but some of them different.
Now what would be the proper way to implement this scenario? Is there a way to make a CarController for the /car/{producer} path which would be a proxy for other controllers like AudiController handling /car/audi requests? Having 3 car producers in one controller and a service for each car producer is ok, but having 30 would make a lot of injected dependencies (30 services injected into the controller) if I would have only one CarController.
I believe node.js Express framework would allow us to do this in the car "controller" script (not sure if they are called controllers in express or what):
var app = express();
app.use('/audi', '{path to audi controller script}');
Is there a similar possibility in Spring? Or is this maybe a bad idea, an antypattern?
Another idea that is quite simple but seems not that elegant is to skip the CarController and implement a:
AudiController with the #RequestMapping("/api/cars/audi")
BmwController with the #RequestMapping("/api/cars/bmw")
MercedesController with the #RequestMapping("/api/cars/mercedes")
etc.
So, what would be the best idea here?
Spring has #PathVariable for this, which can be used in the following way:
#RestController
#RequestMapping("/api/cars/")
public class MyController {
#GetMapping
#RequestMapping("{producer}/cancelOrder")
public String cancelOrder(#PathVariable String producer) {
return "Cancel order for " + producer;
}
}
We can call this endpoint like this: http://localhost:8080/api/cars/Mercedes/cancelOrder
There is no such thing as child controller in Spring. If you want to create a separate controller for every producer having some common functionality, you can use inheritance:
Parent controller class (please note that this does not have any Controller annotation):
#RequestMapping("/api/cars/")
public class BaseCarController {
#GetMapping("/common")
public String common() {
return "Common stuff";
}
}
Child controller classes:
#RestController
public class MercedesController extends BaseCarController{
private MercedesService mercedesService;
// Spring will autowire this by default, no need to add #Autowired
public MercedesController(MercedesService mercedesService) {
this.mercedesService = mercedesService;
}
#GetMapping
#RequestMapping("Mercedes/cancelOrder")
public String cancelOrder() {
return "Cancel order for Mercedes.";
}
}
#RestController
public class AudiController extends BaseCarController{
private AudiService audiService;
// Spring will autowire this by default, no need to add #Autowired
public AudiController(AudiService audiService) {
this.audiService = audiService;
}
#GetMapping
#RequestMapping("Audi/cancelOrder")
public String cancelOrder() {
return "Cancel order for Audi.";
}
}
We can call the produces specific endpoints like this:
http://localhost:8080/api/cars/Mercedes/cancelOrder or http://localhost:8080/api/cars/Audi/cancelOrder. Moreover we can call the common endpoint in the following way: http://localhost:8080/api/cars/common

graphql java: global dataFetcher and dynamic object

I'm trying to use GraphQL like client of a REST API. My backend return JSON but in my application client i write graphQL and in client i translate graphQL queries to HTTP call's.
My schema:
type Query {
students: [Student]
}
type Student {
name: String
}
POJO example:
public class Student {
private Integer id;
private String name;
}
My resolver:
public class Query implements GraphQLQueryResolver {
public List<Post> students() {
// HTTP Request
}
}
In all library's implementations i need create a POJO for Student and write a resolver for request in my API.
A way exist to don't need create a POJO and a create global execute resolver?
If you're using libraries like graphql-java-tools (which seems to be the case), you need POJOs, as this is where the library gets its type mappings from. But if you're just using graphql-java itself, you can wire it any way you like - including having a single global resolver (i.e. DataFetcher).
For an idea how to do this, see http://graphql-java.readthedocs.io/en/latest/schema.html#idl
You want something like:
SchemaParser schemaParser = new SchemaParser();
SchemaGenerator schemaGenerator = new SchemaGenerator();
File schemaFile = loadSchema("yourStudentSchema.graphqls");
TypeDefinitionRegistry typeRegistry = schemaParser.parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
RuntimeWiring is where you hook the resolvers, e.g:
RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
// this uses builder function lambda syntax
.type("Query", typeWiring -> typeWiring
.dataFetcher("students", env -> fetchDataSomeHow(env)))
.build();
}
So you can provide the same DataFetcher implementation to each dataFetcher call f that's what you're after. graphql-java itself makes no assumptions about way it's wired and implemented, e.g. it does not enforce POJOs or anything else.

Java Spring REST API Handling Many Optional Parameters

I'm currently messing around with a Spring Boot REST API project for instructional purposes. I have a rather large table with 22 columns loaded into a MySQL database and am trying to give the user the ability to filter the results by multiple columns (let's say 6 for the purposes of this example).
I am currently extending a Repository and have initialized methods such as findByParam1 and findByParam2 and findByParam1OrderByParam2Desc and etc. and have verified that they are working as intended. My question to you guys is the best way to approach allowing the user the ability to leverage all 6 optional RequestParams without writing a ridiculous amount of conditionals/repository method variants. For example, I want to give the user the ability to hit url home/get-data/ to get all results, home/get-data?param1=xx to filter based on param1, and potentially, home/get-data?param1=xx&param2=yy...&param6=zz to filter on all the optional parameters.
For reference, here is what the relevant chunk of my controller looks like (roughly).
#RequestMapping(value = "/get-data", method = RequestMethod.GET)
public List<SomeEntity> getData(#RequestParam Map<String, String> params) {
String p1 = params.get("param1");
if(p1 != null) {
return this.someRepository.findByParam1(p1);
}
return this.someRepository.findAll();
}
My issue so far is that the way I am proceeding about this means that I will basically need n! amount of methods in my repository to support this functionality with n equalling the amount of fields/columns I want to filter on. Is there a better way to approach handling this, perhaps where I am filtering the repository 'in-place' so I can simply filter 'in-place' as I check the Map to see what filters the user did indeed populate?
EDIT: So I'm currently implementing a 'hacky' solution that might be related to J. West's comment below. I assume that the user will be specifying all n parameters in the request URL and if they do not (for example, they specify p1-p4 but not p5 and p6) I generate SQL that just matches the statement to LIKE '%' for the non-included params. It would look something like...
#Query("select u from User u where u.p1 = :p1 and u.p2 = :p2 ... and u.p6 = :p6")
List<User> findWithComplicatedQueryAndSuch;
and in the Controller, I would detect if p5 and p6 were null in the Map and if so, simply change them to the String '%'. I'm sure there is a more precise and intuitive way to do this, although I haven't been able to find anything of the sort yet.
You can do this easily with a JpaSpecificationExecutor and a custom Specification: https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
I would replace the HashMap with a DTO containing all optional get params, then build the specifications based on that DTO, obviously you can also keep the HashMap and build the specification based on it.
Basically:
public class VehicleFilter implements Specification<Vehicle>
{
private String art;
private String userId;
private String vehicle;
private String identifier;
#Override
public Predicate toPredicate(Root<Vehicle> root, CriteriaQuery<?> query, CriteriaBuilder cb)
{
ArrayList<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(art))
{
predicates.add(cb.equal(root.get("art"), art));
}
if (StringUtils.isNotBlank(userId))
{
predicates.add(cb.equal(root.get("userId"), userId));
}
if (StringUtils.isNotBlank(vehicle))
{
predicates.add(cb.equal(root.get("vehicle"), vehicle));
}
if (StringUtils.isNotBlank(identifier))
{
predicates.add(cb.equal(root.get("identifier"), fab));
}
return predicates.size() <= 0 ? null : cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
// getter & setter
}
And the controller:
#RequestMapping(value = "/{ticket}/count", method = RequestMethod.GET)
public long getItemsCount(
#PathVariable String ticket,
VehicleFilter filter,
HttpServletRequest request
) throws Exception
{
return vehicleService.getCount(filter);
}
Service:
#Override
public long getCount(VehicleFilter filter)
{
return vehicleRepository.count(filter);
}
Repository:
#Repository
public interface VehicleRepository extends JpaRepository<Vehicle, Integer>, JpaSpecificationExecutor<Vehicle>
{
}
Just a quick example adapted from company code, you get the idea!
Another solution with less coding would be to use QueryDsl integration with Spring MVC.
By using this approach all your request parameters will be automatically resolved to one of your domain properties and appended to your query.
For reference check the documentation https://spring.io/blog/2015/09/04/what-s-new-in-spring-data-release-gosling#querydsl-web-support and the example project https://github.com/spring-projects/spring-data-examples/tree/master/web/querydsl
You can do it even more easily using Query By Example (QBE) technique if your repository class implements JpaRepository interface as that interface implements QueryByExampleExecutor interface which provides findAll method that takes object of Example<T> as an argument.
Using this approach is really applicable for your scenario as your entity has a lot of fields and you want user to be able to get those which are matching filter represented as subset of entity's fields with their corresponding values that have to be matched.
Let's say the entity is User (like in your example) and you want to create endpoint for fetching users whose attribute values are equal to the ones which are specified. That could be accomplished with the following code:
Entity class:
#Entity
public class User implements Serializable {
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String city;
private String state;
private String zipCode;
}
Controller class:
#Controller
public class UserController {
private UserRepository repository;
private UserController(UserRepository repository) {
this.repository = repository;
}
#GetMapping
public List<User> getMatchingUsers(#RequestBody User userFilter) {
return repository.findAll(Example.of(userFilter));
}
}
Repository class:
#Repository
public class UserRepository implements JpaRepository<User, Integer> {
}

Spring MVC 3: return a Spring-Data Page as JSON

I have a data access layer made with Spring-Data. I'm now creating a web application on top of it. This one controller method should return a Spring-Data Page formatted as JSON.
Such a Page is a List with additional Paging info like total amount of records and so forth.
Is that possible and if yes how?
And directly related to that can I define the mapping of property names? Eg. meaning I would need define how the paging info properties are named in JSON (differently than in page). Is this possible and how?
There's support for a scenario like this upcoming in Spring HATEOAS and Spring Data Commons. Spring HATEOAS comes with a PageMetadata object that essentially contains the same data as a Page but in a less enforcing manner, so that it can be more easily marshaled and unmarshaled.
Another aspect of the reason we implement this in combination with Spring HATEOAS and Spring Data commons is that there's little value in simply marshaling the page, it's content and the metadata but also want to generate the links to maybe existing next or previous pages, so that the client doesn't have to construct URIs to traverse these pages itself.
An example
Assume a domain class Person:
class Person {
Long id;
String firstname, lastname;
}
as well as it's corresponding repository:
interface PersonRepository extends PagingAndSortingRepository<Person, Long> { }
You can now expose a Spring MVC controller as follows:
#Controller
class PersonController {
#Autowired PersonRepository repository;
#RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}
There's probably quite a bit to explain here. Let's take it step by step:
We have a Spring MVC controller getting the repository wired into it. This requires Spring Data being set up (either through #Enable(Jpa|Mongo|Neo4j|Gemfire)Repositories or the XML equivalents). The controller method is mapped to /persons, which means it will accept all GET requests to that method.
The core type returned from the method is a PagedResources - a type from Spring HATEOAS that represents some content enriched with Links plus a PageMetadata.
When the method is invoked, Spring MVC will have to create instances for Pageable and PagedResourcesAssembler. To get this working you need to enable the Spring Data web support either through the #EnableSpringDataWebSupport annotation about to be introduced in the upcoming milestone of Spring Data Commons or via standalone bean definitions (documented here).
The Pageable will be populated with information from the request. The default configuration will turn ?page=0&size=10 into a Pageable requesting the first page by a page size of 10.
The PageableResourcesAssembler allows you to easily turn a Page into a PagedResources instances. It will not only add the page metadata to the response but also add the appropriate links to the representation based on what page you access and how your Pageable resolution is configured.
A sample JavaConfig configuration to enable this for JPA would look like this:
#Configuration
#EnableWebMvc
#EnableSpringDataWebSupport
#EnableJpaRepositories
class ApplicationConfig {
// declare infrastructure components like EntityManagerFactory etc. here
}
A sample request and response
Assume we have 30 Persons in the database. You can now trigger a request GET http://localhost:8080/persons and you'll see something similar to this:
{ "links" : [
{ "rel" : "next", "href" : "http://localhost:8080/persons?page=1&size=20 }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
Note that the assembler produced the correct URI and also picks up the default configuration present to resolve the parameters into a Pageable for an upcoming request. This means, if you change that configuration, the links will automatically adhere to the change. By default the assembler points to the controller method it was invoked in but that can be customized by handing in a custom Link to be used as base to build the pagination links to overloads of the PagedResourcesAssembler.toResource(…) method.
Outlook
The PagedResourcesAssembler bits will be available in the upcoming milestone release of the Spring Data Babbage release train. It's already available in the current snapshots. You can see a working example of this in my Spring RESTBucks sample application. Simply clone it, run mvn jetty:run and curl http://localhost:8080/pages.
Oliver, your answer is great and I mark it as answer. Here just for completeness what I came up with for the mean time which might be useful for someone else.
I use JQuery Datatables as my grid/table widget. It sends very specific parameter to server and excepts a very specific response: see http://datatables.net/usage/server-side.
To achieve this is created a custom helper object reflecting what datatables expects. Note that getter and setter must be named like they are else the produced json is wrong (case sensitive property names and datatables uses this "pseudo Hungarian notation"...).
public class JQueryDatatablesPage<T> implements java.io.Serializable {
private final int iTotalRecords;
private final int iTotalDisplayRecords;
private final String sEcho;
private final List<T> aaData;
public JQueryDatatablesPage(final List<T> pageContent,
final int iTotalRecords,
final int iTotalDisplayRecords,
final String sEcho){
this.aaData = pageContent;
this.iTotalRecords = iTotalRecords;
this.iTotalDisplayRecords = iTotalDisplayRecords;
this.sEcho = sEcho;
}
public int getiTotalRecords(){
return this.iTotalRecords;
}
public int getiTotalDisplayRecords(){
return this.iTotalDisplayRecords;
}
public String getsEcho(){
return this.sEcho;
}
public List<T> getaaData(){
return this.aaData;
}
}
The second part is a method in the according controller:
#RequestMapping(value = "/search", method = RequestMethod.GET, produces = "application/json")
public #ResponseBody String search (
#RequestParam int iDisplayStart,
#RequestParam int iDisplayLength,
#RequestParam int sEcho, // for datatables draw count
#RequestParam String search) throws IOException {
int pageNumber = (iDisplayStart + 1) / iDisplayLength;
PageRequest pageable = new PageRequest(pageNumber, iDisplayLength);
Page<SimpleCompound> page = compoundService.myCustomSearchMethod(search, pageable);
int iTotalRecords = (int) (int) page.getTotalElements();
int iTotalDisplayRecords = page.getTotalPages() * iDisplayLength;
JQueryDatatablesPage<SimpleCompound> dtPage = new JQueryDatatablesPage<>(
page.getContent(), iTotalRecords, iTotalDisplayRecords,
Integer.toString(sEcho));
String result = toJson(dtPage);
return result;
}
private String toJson(JQueryDatatablesPage<?> dt) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
return mapper.writeValueAsString(dt);
}
compoundService is backed by a Spring-Data repository. It manages transactions and method level security. toJSON() method uses Jackson 2.0 and you need to register the appropriate module to the mapper, in my case for hibernate 4.
In case you have bidirectional relationships, you need to annotate all your entity classes with
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="jsonId")
This enables Jackson 2.0 to serialize circular dependencies (was not possible in earlier version and requires that your entities are annotated).
You will need to add following dependencies:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.2.1</version>
<type>jar</type>
</dependency>
Using Spring Boot (and for Mongo DB) I was able to do the following with successful results:
#RestController
#RequestMapping("/product")
public class ProductController {
//...
#RequestMapping(value = "/all", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE })
HttpEntity<PagedResources<Product>> get(#PageableDefault Pageable p, PagedResourcesAssembler assembler) {
Page<Product> product = productRepository.findAll(p);
return new ResponseEntity<>(assembler.toResource(product), HttpStatus.OK);
}
}
and the model class is like this:
#Document(collection = "my_product")
#Data
#ToString(callSuper = true)
public class Product extends BaseProduct {
private String itemCode;
private String brand;
private String sku;
}

Categories