Currently I am exposing a few Spring Data Repositories as RESTful services by annotating them with #RepositoryRestResource like this:
#RepositoryRestResource(collectionResourceRel = "thing1", path = "thing1")
public interface Thing1Repository extends PagingAndSortingRepository<Thing1, String> {}
#RepositoryRestResource(collectionResourceRel = "thing2", path = "thing2")
public interface Thing2Repository extends CrudRepository<Thing2, String> {}
This all works great. When you hit my first endpoint is also shows all the Spring Data Repositories I have exposed, like this:
{
_links: {
thing1: {
href: "http://localhost:8080/thing1{?page,size,sort}",
templated: true
},
thing2: {
href: "http://localhost:8080/thing2"
}
}
}
Now I have some endpoints I want to expose that cannot be represented by Spring Data Repositories, so I am using a RestController.
Here is a simple example:
#RestController
#ExposesResourceFor(Thing3.class)
#RequestMapping("/thing3")
public class Thing3Controller {
#Autowired
EntityLinks entityLinks;
#Autowired
Thing3DAO thing3DAO;
//just assume Thing3.class extends ResourceSupport. I know this is wrong, but it makes the example shorter
#RequestMapping(value = "/{id}", produces = "application/json")
Thing3 thing3(#PathVariable("id") String id)
{
Thing3 thing3 = thing3DAO.findOne(id);
Link link = entityLinks.linkToSingleResource(Thing3.class, id);
thing3.add(link);
return thing3;
}
}
Now if I run this app and go to:
http://localhost:8080/thing3/{id}
I do get a JSON representation of the Thing3 with a link to itself, that works as expected.
What I want to figure out how to do is have the first endpoint also describe this controller. I basically want this:
{
_links: {
thing1: {
href: "http://localhost:8080/thing1{?page,size,sort}",
templated: true
},
thing2: {
href: "http://localhost:8080/thing2"
},
thing3: {
href: "http://localhost:8080/thing3"
}
}
}
What do I need to do to get my base endpoint to have a link to this controller?
You could override RepositoryLinkResource, and add a resource pointing to your thing3:
resource.add(ControllerLinkBuilder.linkTo(Thing3Controller.class).withRel("thing3"));
Check this question: Custom response for root request int the Spring REST HATEOAS with both RepositoryRestResource-s and regular controllers
Related
I finally found a way to override methods of Spring Data REST with a custom implementation. Unfortunately this disables the default handling.
My Repository should contain findAll and findById exposed over the GET: /games and GET: /games/{id} respectively and save should not be exported because it is overriden by the controller.
#RepositoryRestResource(path = "games", exported = true)
public interface GameRepository extends Repository<Game, UUID> {
Collection<Game> findAll();
Game findById(UUID id);
#RestResource(exported = false)
Game save(Game game);
}
My controller should handle POST: /games, generate the game on the server and return the saved Game.
#RepositoryRestController
#ExposesResourceFor(Game.class)
#RequestMapping("games")
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces = "application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
However when I try to GET: /games it returns 405: Method Not Allowed but POST: /games works as intended. When I change the value of the generateNewGame mapping to "new" all three requests work. But POST: /games/new is no RESTful URL Layout and I would rather avoid it. I don't understand why I get this behaviour and how I may solve it. Does anybody have a clue?
Use #BasePathAwareControllerannotation above your controller to preserve default spring data rest paths and add new custom path base on your need. Although overwrite default spring data rest path.
#BasePathAwareController
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces =
"application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO
createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
Maybe you can do something we usually do in Linux. Set a fake path and link to it.
POST /games ==> [filter] request.uri.euqal("/games") && request.method==POST
==> Redirect /new/games
What you see also is /games.
Don't use /games/new, it may be conflict with things inner Spring.
My REST API must work with gRPC objects as input parameters.
The most simple example is:
GET http://localhost:8083/api/books?page.number=1&page.size=30
where the proto definition is:
message PagedMessage {
Page page = 1;
}
message Page {
int32 number = 1;
int32 size = 2;
}
The controller is:
#RequestMapping(value = "/api/books")
public class ObjectOps {
#Autowired
private BooksService booksService;
#GetMapping(value = "/")
#ResponseBody
BooksList listBooks(#RequestParam PagedMessage request) {
return booksService.getBooks(request);
}
}
And in the application I have this bean:
#Bean
ProtobufJsonFormatHttpMessageConverter protobufJsonFormatHttpMessageConverter() {
return new ProtobufJsonFormatHttpMessageConverter();
}
The only way it worked for me is to pass the paging information as GET body:
{
"page" : {
"number": 1,
"size": 30
}
}
but it will be great to have the list books method object be populated from the request path parameters.
I think you can just remove the #RequestParam annotation and Spring will populate the object.
Referenced by this answer: https://stackoverflow.com/a/16942352/8075423
I want to retrieve the annotations of a service ( in particular #RolesAllowed ) given the URI corresponding to the service.
Here an example:
The service:
#GET
#Path("/example")
#RolesAllowed({ "BASIC_USER", "ADMIN" })
public Response foo() {
//Service implementation
}
I want to retrieve { "BASIC_USER", "ADMIN" } given the String "/example".
I use RestAssured for testing so, if possible, I prefer a solution with the latter.
Thank you.
I am not familiar with RestAssured, but I wrote the following Junit test and it works. Perhaps you can adapt it to work with RestAssured.
First the Service:
public class Service {
#GET
#Path("/example")
#RolesAllowed({ "BASIC_USER", "ADMIN" })
public Response foo() {
return new Response();
}
}
And this is the corresponding Junit test:
#Test
public void testFooRoles() throws Exception {
Method method = Service.class.getMethod("foo");
Annotation path = method.getDeclaredAnnotation(javax.ws.rs.Path.class);
assertTrue(((Path) path).value().equals("/example"));
RolesAllowed annotation = method.getDeclaredAnnotation(RolesAllowed.class);
List<String> roles = Arrays.asList(annotation.value());
assertEquals(2, roles.size());
assertTrue(roles.contains("BASIC_USER"));
assertTrue(roles.contains("ADMIN"));
}
I have use spring boot actuator health with spring fox swagger in a spring boot projet. I use below in my Application.java class.
#Autowired
private HealthAggregator healthAggregator;
#Autowired
private Map<String, HealthIndicator> healthIndicators;
#Bean
public com.health.TestMeHealthEndpoint getHealthEndpoint() {
return new com.health.TestMeHealthEndpoint(healthAggregator, healthIndicators);
}
#Bean
public Docket testMeApi() {
return new Docket(DocumentationType.SWAGGER_2).useDefaultResponseMessages(false).apiInfo(apiInfo()).select()
.paths(testMePaths()).build();
}
private Predicate<String> testMePaths() {
return or(regex("/api/myservice1"), regex("/health"));
}
But when I check the swagger ui, I see multiple end points for health with all types of http methods include POST,DELETE, OPTIONS etc. For myservice1 which implement in the REST contoller, it only display the GET method.
The TestMeHealthEndpoint extends AbstractEndpoint and overide invoke method with custom health information.
I only want to see is the GET method for the health route?
Add source of TestMeHealthEndpoint:
#ConfigurationProperties(prefix = "endpoints.health", ignoreUnknownFields = true)
public class TestMeHealthEndpoint extends AbstractEndpoint<Health> {
//Some getter and setters for api name , version etc
public TestMeHealthEndpoint (final HealthAggregator healthAggregator,
final Map<String, HealthIndicator> healthIndicators) {
super("health", false);
final CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(healthAggregator);
for (final Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
healthIndicator.addHealthIndicator(getKey(entry.getKey()), entry.getValue());
}
this.healthIndicator = healthIndicator;
}
#Override
public Health invoke() {
final Health health = new Health();
health.setStatus(this.healthIndicator.health().getStatus().getCode());
health.setName(this.apiName);
health.setVersion(this.apiVersion);
final UriComponentsBuilder path = ServletUriComponentsBuilder.fromCurrentServletMapping()
.path(this.managementContextPath).pathSegment(this.getId());
health.add(new Link(path.build().toUriString()).withSelfRel());
return health;
}
}
I would like to suggest you a little workaround. To create rest controller which will be delegate requests to the Health endpoint. Something like this:
#RestController
public class HealthController {
#Autowired
TestMeHealthEndpoint testMeHealthEndpoint;
#ApiOperation(value="Health endpoint", notes = "Health endpoint")
#RequestMapping(value = "/health", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ApiResponses(value = {#ApiResponse(code = 200, message = "OK")})
public ResponseEntity<Health> invoke() {
return ResponseEntity.ok(testMeHealthEndpoint.invoke());
}
}
In that way you also can use following directive for swagger:
.select().apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
Swagger assumes that if no #RequestMapping method is set, any method is ok. Add method = RequestMethod.GET to your RequestMapping braces ().
f you add a #Bean of type Endpoint then it will automatically be exposed over JMX and HTTP (if there is an server available). An HTTP endpoints can be customized further by creating a bean of type MvcEndpoint. Your MvcEndpoint is not a #Controller but it can use #RequestMapping (and #Managed*) to expose resources.
http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html
I'm using Spring Boot 1.3.1.RELEASE and trying to create a custom Repository + Controller. I configured the basePath to be /api and cannot figure out how to put the custom Controller's URI to automatically be relative to basePath. Is there some piece of magic I'm missing?
Here's the controller. I've tried every combination of the attributes below as well. I left in the commented out ones so you can see what I've tried.
#RepositoryRestController
// #Controller
#ExposesResourceFor(AnalystSummaryModel.class)
// #RequestMapping("/analystsummaries")
public class AnalystSummaryController {
#Autowired
AnalystSummaryRepository repository;
#Autowired
private AnalystSummaryResourceAssembler resourceAssembler;
#ResponseBody
#RequestMapping(method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public PagedResources<AnalystSummaryModel> getAnalystSummaries(
final Pageable pageable,
final PagedResourcesAssembler assembler) {
final Page<AnalystSummaryModel> analystSummaries = repository.findAll(pageable);
return assembler.toResource(analystSummaries, resourceAssembler);
}
}
I also create a ResourceProcessor based on another question.
When I view the /api endpoint, I see the following:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080"
},
"profile" : {
"href" : "http://localhost:8080/api/profile"
}
}
}
When I uncomment the #RequestMapping, I then get:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/analystsummaries"
},
"profile" : {
"href" : "http://localhost:8080/api/profile"
}
}
}
What am I missing to have the mapping be relative to basePath, which I set in application.yml to the following?
spring.data.rest.base-path: /api
Some more information:
Using #BasePathAware actually results in this controller service two different URIs! It shows up at / as well as /api/analystSummaries (because of the auto-pluralization, etc). Then when using ControllerLinkBuilder it uses the path of the first. So my updated question is: Why is this exposed twice? How can I eliminate the implicit root (since there is no #RequestMapping) and keep the one that is under /api?
Get rid of spring.data.rest.base-path: /api and add this:
server.servlet-path: /api
This can be done with a specific configuration since Spring boot 1.4.0.RC1.
See my answer at How to configure a default #RestController URI prefix for all controllers?