Handle subclass of arraylist as an array - jersey

We have an Jersey REST-API annotated with Swagger annotations.
The swagger UI works with the exception of one class. This class extends ArrayList<T>. And I expect that this class is handled by swagger as an array. But this is not the case. It is handled like any other complex object and its public members are shown in swagger-ui.
Here a code summary of my class:
#JsonAutoDetect(getterVisibility = Visibility.NONE, fieldVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class ArraySubClass extends ArrayList<ElementClass> implements IAnInterface {
// constructors...
public A getA() {
// ...
}
}
Swagger shows this class as model object:
"ArraySubClass": {
"id": "ArraySubClass",
"description": "",
"properties": {
"a": {
"$ref": "A"
},
"empty": {
"type": "boolean"
},
"size": {
"type": "integer",
"format": "int32"
}
}
}
What can I do, so that swagger does handle the class (extending from ArrayList<T>) as an array and that swagger just shows its elements and not other properties?

you can rather use the class ElementClass directly in swagger as this
#ApiOperation(value = "/yourEndPoint", notes = "comment for endpoint", response = ElementClass.class, responseContainer = "List")
So that you'll present the content of your class as a list in the return and not your ArraySubClass that may contains others datas / stuff you want to hide ! :)
For more info : see response container

Related

How to customize Spring Data REST to use a custom path for a repository resource?

I'm developing a microservice using SPRING DATA REST where I need to sub-resources to be accessible only thru main resource like the following structure :
http://localhost:8080/posts
http://localhost:8080/posts/1/comments
and block direct access to sub-resource directly like this http://localhost:8080/comments/*.
where comments must be accessible only thru related poste , I did the following in my repository :
#RepositoryRestResource(collectionResourceRel = "posts", path = "posts")
public interface PostRepository extends PagingAndSortingRepository<Post, Long> {
...
}
for comments :
#RepositoryRestResource(collectionResourceRel = "comments", path = "comments")
public interface CommentRepository extends PagingAndSortingRepository<Comment, Long> {
...
}
now by default SPRING DATA REST returns the following results when i goto to : http://localhost:8080
{
"id": 1,
"content": "some post text .........................",
"author":"abc",
"_links": {
"self": {
"href": "http://localhost:8080/posts/1"
},
"attributes": {
"href": "http://localhost:8080/posts/1/comments"
}
}
}
now if i want to post a comment i need to do the following :
http://{server:port}/comment METHOD:POST
{"author":"abc","content":"PQROHSFHFSHOFSHOSF", "post":"http://{server:port}/post/1"}
but what i need to achienve is to POST to an url like this http://{server:port}/posts/1/comment where the POST is the root resource NOT like the previous path http://{server:port}/comment
http://{server:port}/posts/1/comment METHOD:POST
{"author":"abc","content":"PQROHSFHFSHOFSHOSF", "post":"http://{server:port}/post/1"}
i now that this is possible if a create a custom comment #controller but i want to use the already building features of SPRING DATA REST and Hateoas support .

Spring Data Rest - customize endpoint name

By default, Spring data REST use camelCase for endpoint names.
For example, if the repository is
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends PagingAndSortingRepository<User, Integer> {
List<User> findByUsername(#Param("username") String username);
}
Then the endpoint is http://localhost:8080/users/search/findByUsername?username=test
How can I customize the endpoint so it use snake_case, became like this: http://localhost:8080/users/search/find_by_username?username=test
Or even different method name
change find_by_username : http://localhost:8080/users/search/by_username?username=test
(stripping the find_by_username): http://localhost:8080/users/search?username=test
Thanks
The #RestResource annotation also gives us the ability to customize the URL path mapped to a repository method and the link id in the JSON returned by the HATEOAS resource discovery.
To do that, we use the optional parameters of the annotation:
path for the URL path
rel for the link id
By executing a cUrl to http://localhost:8080/users/search/, we can now see our new method listed with other resources:
{
"_links": {
"findByUsername": {
"href": "http://localhost:8080/users/search/findByUsername{?username}"
},
"self": {
"href": "http://localhost:8080/users/search/"
}
}
}
So to customize the rest url endpoint, we can simply add the #RestResource annotation:
#RestResource(path = "byUsername", rel = "customFindMethod")
List<User> findByUsername(#Param("username") String username);
If we do the resource discovery again, the resulting JSON will confirm our changes:
{
"_links": {
"customFindMethod": {
"href": "http://localhost:8080/users/search/byUsername{?username}",
"templated": true
},
"self": {
"href": "http://localhost:8080/users/search/"
}
}
}
For more details you can check her

How to return #RepositoryRestResource style responses from a #RestController

Using the #RepositoryRestResource generates paths and injects all the necessary HATEOAS links for a REST API, but when I return the same results from the repository using a controller the JSON structure is different and there are no HATEOAS links.
How would I return get the same JSON structure from a controller as the RepositoryRestResource generated paths?
// /accounts (RepositoryRestResource JSON structure)
{
_embedded: {
accounts: []
},
_links: {
first: {},
self: {},
next: {},
last: {},
profile: {},
search: {}
},
page: {
size: 20,
totalElements: 35,
totalPages: 2,
number: 0
}
}
// /my-accounts (RestController JSON structure)
{
content: [ ... ], // accounts
pageable: {},
totalPages: 1,
totalElements: 2,
last: true,
size: 20,
number: 0,
sort: {},
numberOfElements: 2,
first: true
}
REST Repository:
#RepositoryRestResource(collectionResourceRel = "accounts", path = "accounts", itemResourceRel = "account")
public interface AccountRepository extends PagingAndSortingRepository<Account, Long> {
#RestResource(path = "owner", rel = "owner")
Page<Account> findByOwner(#Param("username") String owner,Pageable p);
}
REST Controller:
#RestController
public class AccountController {
private AccountRepository repo;
#Autowired
public AccountController(AccountRepository repo) {
this.repo = repo;
}
#RequestMapping(
path = "/my-accounts",
method = RequestMethod.GET,
produces = "application/hal+json")
public ResponseEntity<Page<Account>> getStatus(
#RequestParam(value = "page", defaultValue = "0", required = false) int page,
#RequestParam(value = "size", defaultValue = "20", required = false) int size,
Authentication authentication) {
String username = authentication.getName();
Page<Account> accounts = repo.findByOwner(username, PageRequest.of(page, size));
return ResponseEntity.ok(accounts);
}
}
Basically, Spring Data REST is just a default implementation of a boilerplate code (like controllers) which people usually write exposing Spring Data repositories via REST and using Spring HATEOAS, i.e. trying to reproduce exactly the same effect with your hand-written controller means just writing the whole Spring Data REST on your own, so, it is a bad idea. Luckily, some parts are easy to reproduce though.
If you speak only about adding paging links to your controller's output (and not implementing other things like search controller, to which a link is present in your sample Spring Data REST controller output), you may take a look at how Spring Data REST does it. It uses Spring Data's PagedResourcesAssembler, which accepts a Page and creates a HATEOAS resource with the required navigation links.
So, to add the paging links, you must inject a PagedResourcesAssembler instance in your controller and use it:
public ResponseEntity<PagedResources> getStatus(
#RequestParam(value = "page", defaultValue = "0", required = false) int page,
#RequestParam(value = "size", defaultValue = "20", required = false) int size,
Authentication authentication,
PagedResourcesAssembler assembler) {
String username = authentication.getName();
Page<Account> accounts = repo.findByOwner(username, PageRequest.of(page, size));
return ResponseEntity.ok(assembler.toResource(accounts));
}
Not sure, but this might do the trick:
return ResponseEntity.ok(new org.springframework.hateoas.Resource<>(accounts));
If not then you could wrap the accounts in a class that extends ResourceSupport. So just create some class AccountSupport extends ResourceSupport and add the required links in there. It has a lot of utility methods like
add(linkTo(AccountController.class).withSelfRel());
or for links to individual Accounts:
add(linkTo(AccountController.class).slash(idOfYourAccountInstance).withSelfRel())

Spring Data Rest, SpringFox and JpaRepository custom finders

NB: using Spring Boot 1.4.2 + SpringFox 2.6.0
Hi, I'm having an issue with Swagger 2 forms on my API documentation over a #RepositoryRestResource. The code below works fine (REST access OK):
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
Person findByLastName(#Param("name") String name);
}
And the HATEOAS links are right too: calling URL /api/people/search
ends up with this (notice parameter "name"):
{
"_links": {
"findByLastName": {
"href": "http://localhost:8080/api/people/search/findByLastName{?name}",
"templated": true
},
"self": {
"href": "http://localhost:8080/api/people/search"
}
}
}
The REST API is ok: URL /api/people/search/findByLastName?name=foobar returns data when executed with a browser
BUT in Swagger the GET parameter type is interpreted as "body" instead of "query" and the form submission (curl ... -d 'foobar'...) fails in 404, attempting to submit "name" as request body.
So I tried to set Swagger explicitly, like this:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
#ApiOperation("Find somebody by it's last name")
#ApiImplicitParams({
#ApiImplicitParam(name = "name", paramType = "query")
})
Person findByLastName(#Param("name") #ApiParam(name = "name") String name);
}
without any success, despite the fact that "name" is well retained in the form as the parameter name in this example :-(
body parameter type on GET query
Does anyone know what could be done to make that Swagger form to work? Thx for your help
This is it : #Param configures Spring Data REST, while #RequestParam fits Swagger
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends JpaRepository<Person, Long> {
// #Param Spring Data REST : Use #Param or compile with -parameters on JDK 8
// #RequestParam Swagger : paramType=query cf. $Api*Param
Person findByLastName(#Param("name") #RequestParam("name") String name);
}
Me happy!

spring hateoas generates different responses for collection or pojo

I have two classes
import org.springframework.hateoas.ResourceSupport;
public class A{}
public class B{}
public class AResource extends ResourceSupport {
private final A a;
}
public class BResource extends ResourceSupport {
private final B b;
}
#Controller
public class Controller {
#RequestMapping
#ResponseBody
public Set<AResource> giveMeColl() {
}
#RequestMapping
#ResponseBody
public BResource giveMeSingle() {
}
}
both responses add links object but for resource A is "links" and for resource B is "_link" and also structure changes
//RESPONSE FOR A
[
{
"content":{
//my fancy object
},
"links":[
{
"rel": "self",
"href": "http://localhost:8080/myid/22"
}
]
}
]
{
"content":{
//my fancy object
},
"_links":[
{
"self": "http://localhost:8080/myid/22/someelse/33"
}]
}
both resources are constructed with assemblers and both are adding the link from the ids
AResource aresource = new AResource(a);
resource.add(linkTo(methodOn(Controller.class).giveMeColl()).withSelfRel());
BResource bresource = new BResource(b);
resource.add(linkTo(methodOn(Controller.class).giveMeSingle()).withSelfRel());
Response headers for a is
"content-type": "application/json;charset=UTF-8"
and for b
"content-type": "application/hal+json;charset=UTF-8"
Could it be because returning an array is not really Restful? as Some post suggest
p.s. I have added and removed #EnableHypermediaSupport but doesn't seem to affect the problem.
"_links" follows the HAL specification. Spring HATEOAS includes a dedicated serializer for that, but it is only used for classes that extend ResourceSupport.
Returning a simple array is not exactly "unRESTful", but it doesn't meet the REST maturity level 3 (Hypermedia controls). In order to achieve that you can wrap the collection into a Resources instance, which extends ResourceSupport. You should get the same link serialization for both types then.

Resources