With Swagger-UI I am trying to display methods that have the same base URL underneath one Swagger container regardless of which controller they fall under. Imagine this code.
// Current Swagger Config Class
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(getPaths())
.build()
.apiInfo(getApiInfo());
}
private Predicate<String> getPaths(){
return or(regex("/attributes.*"),
regex("/other.*"));
}
// Controller 1
#RestController
#RequestMapping("/attributes")
#Api(value = "User Attribute Services", description = "User Attributes API")
public class UserAttributesController {
#Autowired
private UserAttributesService userService;
#Autowired
private UserAttributeValidator userAttributeValidator;
#RequestMapping(value = "/user/search", method = RequestMethod.POST, produces = "application/json")
public List<User> searchUser(#RequestBody List<UserDTO> userDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/user/insert", method = RequestMethod.POST)
public boolean insertUser(#RequestBody List<UserDTO> userDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/user/update{id}", method = RequestMethod.PATCH)
public boolean updateUser(#PathVariable id, #RequestBody UserDTO updateDetails){
// meaningless implementation ...
}
#RequestMapping(value = "/user/delete/{id}", method = RequestMethod.DELETE)
public boolean deleteUser(#PathVariable id){
// meaningless implementation ...
}
}
// Controller 2
#RestController
#RequestMapping("/attributes")
#Api(value = "Tech Attribute Services", description = "Tech Attributes API")
public class TechAttributesController {
#Autowired
private TechAttributesService techService;
#Autowired
private TechAttributeValidator techAttributeValidator;
#RequestMapping(value = "/tech/search", method = RequestMethod.POST, produces = "application/json")
public List<Tech> searchTech(#RequestBody List<TechDTO> techDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/insert", method = RequestMethod.POST)
public boolean insertTech(#RequestBody List<TechDTO> techDTOList){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/update{id}", method = RequestMethod.PATCH)
public boolean updateTech(#PathVariable id, #RequestBody TechDTO updateDetails){
// meaningless implementation ...
}
#RequestMapping(value = "/tech/delete/{id}", method = RequestMethod.DELETE)
public boolean deleteTech(#PathVariable id){
// meaningless implementation ...
}
}
My application swagger page is displaying the attributes underneath seperate containers. Eventhough I specified the regex path of '/attributes' in my Swagger Configuration the fact that these are in different controllers is overriding what I want. If I put all these methods into 1 controller I get the desired result, but this is only a subset of the attribute classes I have and I do not want a massive controller that spans over 1k lines.
Extra: If you noticed the services are almost identical for the attributes. I have been trying to find a clever way of declaring them once and simply overriding the method declations instead of redefining similiar services over again, but because the #RequestMapping annotation needs to be a constant value I could not find a way to change the annotation depending on the implementation rather than the declaration.
Swagger-UI Picture
Related
I'm trying to return JSON/XML from a function of my Controller. First I was using #RestController and it worked good, but now I need to change to #Controller, because I will use also some other functions and pass there some objects for my view.
#Controller
#RequestMapping("/game")
public class ViewController {
#RequestMapping(value = "/statistic", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public GamerData[] getStatistic() {
RestTemplate restTemplate = new RestTemplate();
try {
String uri_get_statistic = "http://localhost:8081/statistic/";
ResponseEntity<GamerData[]> response = restTemplate.getForEntity(uri_get_statistic, GamerData[].class);
GamerData[] statisticData = response.getBody();
return statisticData;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
After I've changed to Controller I get error 404 not found. With RestController I've got json. (GamerData is just a class with 2 simple fields (int and String), setters, getters, consructor).
UPDATE:
I've added #ResponseBody to my function, but now I have Internal Server error
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class [Lgame.mainservice.mvc.GamerData;] with preset Content-Type 'null']
try to add #ResponseBody like
public #ResponseBody GamerData[] getStatistic....
#RequestMapping(value = "/statistic", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#ResponseBody // add this one
public GamerData[] getStatistic() {}
Thank you all for answers, I've added #ResponceBody annotation to my function and removed "produces" part from #RequestMapping annotation and my function works great:
#RequestMapping(value = "/statistic", method = RequestMethod.GET)
#ResponseBody
public GamerData[] getStatistic() {
//function code
}
I have a rest controller with one method. This method takes one String argument annotated as #RequestBody. For some reason not mentioned here, I'm forced to use type String and manually convert it to TestDTO. From the API's consumer point of view body is type of TestDTO and I want to show this type in SwaggerUI.
Unfortunately (which is quite obvious) swagger shows that body is type of String. Look at the picture below.
What I want to achieve is to have String body in java code and TestDTO in swagger code. How can I force Swagger to show it? I tried to find annotations and its properties, but failed.
Rest controller code below:
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
class TestDTO{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
I figured it out. Two changes need to be made.
First, like in #Dave Pateral's answer #ApiImplicitParams must be added
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#ApiImplicitParams({
#ApiImplicitParam(name = "requestBody", required = true,
dataType = "TestDTO", paramType = "body")
})
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
And then implicit Model must be registered in the docket, minimal working example below
#Configuration
public class SwaggerConfiguration {
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.additionalModels(typeResolver.resolve(TestDTO.class));
}
}
And the result is
Try put this annotation on your method:
#ApiImplicitParam(name = "test", value = "testDTO", required = true, dataType = "TestDTO")
I'm attempting to add some additional business logic to the auto-generated endpoints from the RepositoryRestResource. Please see the code below:
Resource:
#RepositoryRestResource(collectionResourceRel="event", path="event")
public interface EventRepository extends PagingAndSortingRepository<Event, Long> {
}
Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private PagedResourcesAssembler<Event> pagedResourcesAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<PersistentEntityResource> getEvents(Pageable pageable,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Page<Event> events = eventRepository.findAll(pageable);
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
}
}
I've looked at the following two stackoverflow articles:
Can I make a custom controller mirror the formatting of Spring-Data-Rest / Spring-Hateoas generated classes?
Enable HAL serialization in Spring Boot for custom controller method
I feel like I am close, but the problem that I am facing is that:
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
returns an error saying:
"The method toResource(Page<Event>, Link) in the type PagedResourcesAssembler<Event> is not applicable
for the arguments (Page<Event>, PersistentEntityResourceAssembler)".
The toResource method has a method signature that accepts a ResourceAssembler, but I'm not sure how to properly implement this and I can't find any documentation on the matter.
Thanks in advance,
- Brian
Edit
My issue was that I thought I could override the controller methods that are auto-created from #RepositoryRestResource annotation without having to create my own resource and resource assembler. After creating the resource and resource assembler I was able to add my business logic to the endpoint.
Resource:
public class EventResource extends ResourceSupport {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Resource Assembler:
#Component
public class EventResourceAssembler extends ResourceAssemblerSupport<Event, EventResource> {
public EventResourceAssembler() {
super(EventController.class, EventResource.class);
}
#Override
public EventResource toResource(Event entity) {
EventResource eventResource = createResourceWithId(entity.getId(), entity);
eventResource.setName(entity.getName());
return eventResource;
}
}
Updated Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private EventResourceAssembler eventResourceAssembler;
#Autowired
private PagedResourcesAssembler<Event> pageAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<EventResource> getEvents(Pageable pageable) {
Page<Event> events = eventRepository.findAll(pageable);
// business logic
return pageAssembler.toResource(events, eventResourceAssembler);
}
}
The thing I don't like about this is that it seems to defeat the purpose of having a RepositoryRestResource. The other approach would be to use event handlers that would get called before and/or after the create, save, delete operations.
#RepositoryEventHandler(Event.class)
public class EventRepositoryEventHandler {
#HandleBeforeCreate
private void handleEventCreate(Event event) {
System.out.println("1");
}
}
There doesn't seem to be any events for the findAll or findOne operations. Anyways, both these approaches seem to solve my problem of extending the auto generated controller methods from RepositoryRestResource.
It requires a PagedResourcesAssembler, Spring will inject one for you if you ask.
public PagedResources<Foo> get(Pageable page, PagedResourcesAssembler<Foo> assembler) {
// ...
}
In this case the resource is Foo. It seems in your case the resource you're trying to return is an Event. If that's so, I would expect your code to look something like:
private ResourceAssembler<Event> eventAssembler = ...;
public PagedResources<Event> get(Pageable page, PagedResourcesAssembler<Event> pageAssembler) {
Event event = ...;
return eventAssembler.toResource(event, pageAssembler);
}
You provide the ResourceAssembler<Event> that tells Spring how to turn Event into a Resource. Spring injects the PagedResourcesAssembler<Event> into your controller method to handle the pagination links. Combine them by calling toResource and passing in the injected pageAssembler.
The final result can be returned simply as a body as above. You could also use things like HttpEntity to gain more control over status codes and headers.
Note: The ResourceAssembler you provide can literally be something as simple as wrapping the resource, such as Event, with a Resource object. Generally you'll want to add any relevant links though.
To hack it you can use just PagedResourcesAssembler<Object> like:
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedModel<PersistentEntityResource> getEvents(
Pageable pageable,
PersistentEntityResourceAssembler persistentAssembler,
PagedResourcesAssembler<Object> pageableAssembler
) {
return pageableAssembler.toModel(
(Page<Object>) repository.findAll(pageable),
persistentAssembler
);
}
I am pretty new in Spring MVC and I have the following doubt.
In a controller, I have a method annotated in this way:
#Controller
#RequestMapping(value = "/users")
public class UserController {
#RequestMapping(params = "register")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
So this method handle HTTP Request toward the URL /users?register where register is a parameter (because the entire class handle request toward /users resource).
Is it the same thing if, instead using the params = "register" I use the following syntaxt:
#Controller
public class UserController {
#RequestMapping("/users/{register}")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
I have deleted the mapping at class level and I use #RequestMapping("/users/{register}").
Is it the same meaning of the first example?
NO, they are completely different constructs:
Code 1
#Controller
#RequestMapping(value = "/users")
public class UserController {
#RequestMapping(params = "register")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
In this case, createForm method will be called when a HTTP request is made at URL /users?register. Quoting from Spring Javadoc, it means this method will be called whatever the value of the register HTTP parameter; it just has to be present.
"myParam" style expressions are also supported, with such parameters having to be present in the request (allowed to have any value).
Code 2
#Controller
public class UserController {
#RequestMapping("/users/{register}")
public String createForm(Model model) {
model.addAttribute("user", new Customer());
return "user/register";
}
}
In this case, #RequestMapping is declaring register as a PathVariable. The method createForm will be called if a HTTP request is made at URL /users/something, whatever the something. And you can actually retrieve this something like this:
#RequestMapping("/users/{register}")
public String createForm(#PathVariable("register") String register, Model model) {
// here "register" will have value "something".
model.addAttribute("user", new Customer());
return "user/register";
}
I'm trying out Spring MVC 3.0 for the first time and like to make it RESTfull.
This is my controller:
#Controller
#RequestMapping(value = "/product")
#SessionAttributes("product")
public class ProductController {
#Autowired
private ProductService productService;
public void setProductValidator(ProductValidator productValidator, ProductService productService) {
this.productService = productService;
}
#RequestMapping(method = RequestMethod.GET)
public Product create() {
//model.addAttribute(new Product());
return new Product();
}
#RequestMapping(method = RequestMethod.POST)
public String create(#Valid Product product, BindingResult result) {
if (result.hasErrors()) {
return "product/create";
}
productService.add(product);
return "redirect:/product/show/" + product.getId();
}
#RequestMapping(value = "/show/{id}", method = RequestMethod.GET)
public Product show(#PathVariable int id) {
Product product = productService.getProductWithID(id);
if (product == null) {
//throw new ResourceNotFoundException(id);
}
return product;
}
#RequestMapping(method = RequestMethod.GET)
public List<Product> list()
{
return productService.getProducts();
}
}
I have 2 questions about this.
I'm a believer in Convention over Configuration and therefor my views are in jsp/product/ folder and are called create.jsp , list.jsp and show.jsp this works relatively well until I add the #PathVariable attribute. When I hit root/product/show/1 I get the following error:
../jsp/product/show/1.jsp" not found how do I tell this method to use the show.jsp view ?
If I don't add the RequestMapping on class level my show method will be mapped to root/show instead of root/owner/show how do I solve this ? I'd like to avoid using the class level RequestMapping.
add your 'product' to Model and return a String /product/show instead of Product. In your show.jsp, you can access the product object form pageContext
Check out the section in the manual about "Supported handler method arguments and return types".
Basically, when your #RequestMapping method returns just an object, then Spring uses this as a single model attribute, and, I'm guessing, attempts to use the request URL as the basis for the view name.
The easiest way to return the view and data you want from the same method is probably to just have the method return a ModelAndView, so you can explicitly specify the viewName and the model data.