swagger documentation for endpoint with url pattern - spring-boot

I want to create swagger documentation for an endpoint with ** pattern. In swagger-ui page I find the endpoint but there isn't any input for url of **. My endpoint signature is:
#GetMapping("/{category}/**")
public DocumentModel fetch(HttpServletRequest httpServletRequest, #PathVariable("category") String category)
I want to call above endpoint by myCategory/test/document1 address but there isn't any placeholder for test/document1. How can I call this endpoint by swagger?

Unfortunately, it is not doable. Swagger supports neither wildcards in the path, nor optional path segments, nor slashes in path parameter values. If you look at the request that swagger-ui executes for your mapping, you will see something like this:
curl -X GET "http://localhost:8080/category1/**"
The stars are not replaced, they are treated as a normal string, not as a placeholder.
I am afraid your only option is modifying the endpoint or adding a new one, e.g:
#GetMapping("/{category}/test/{document}")
#Parameter(name = "document", in = ParameterIn.PATH)
public DocumentModel fetchTest(HttpServletRequest httpServletRequest, #PathVariable("category") String category) {
return fetch(httpServletRequest, category);
}
#GetMapping("/{category}/**")
public DocumentModel fetch(HttpServletRequest httpServletRequest, #PathVariable("category") String category) {
// ...
}
If you need to, you can hide the original endpoint from the swagger-ui:
#GetMapping("/{category}/**")
#Operation(hidden = true)
public DocumentModel fetch(HttpServletRequest httpServletRequest, #PathVariable("category") String category) {
// ...
}

Related

Spring Boot Squiggly library intercept and modify request parameter before reaching controller

I want to filters the attributes being sent in json response for multiple requests. I used Squiggly library to achieve this. Below is the config level code added to achieve this.
#Configuration
#ConditionalOnClass(ObjectMapper.class)
public class SquigglyAutoconfigure {
#Bean
public FilterRegistrationBean squigglyRequestFilter(ObjectMapper objectMapper)
{
Squiggly.init(objectMapper, new RequestSquigglyContextProvider());
FilterRegistrationBean<SquigglyRequestFilter> filter = new FilterRegistrationBean<>();
filter.setFilter(new SquigglyRequestFilter());
filter.setOrder(1);
return filter;
}
}
Now I have a working code that responds only the fields sent in query parameters as "fields". Eg - inventory/stock?fields=stockInformation will only return field stockInformation in response.
Now, front-end can send query prameter "fields" to receive only required fields.
But I also want to define a default set of fields for each request. i.e. if front-end do not sent queryparam "fields", i should be able to initialise fields with custom values. I tried adding defaultValue in contoller but it didn't worked.
Below code didn't worked
#PostMapping
#ResponseStatus(HttpStatus.OK)
public StockInformation returnAllStock(#RequestBody FilterDataList filterDataList,
#RequestParam(name = "fields", required = false,defaultValue="stockInformation{productId}") String fields)
Is there a way where I can intercept each request and initialise request param "fields"?
If you intialize RequestSquigglyContextProvider, you should be able to acheive this
The key is the new RequestSquigglyContextProvider("fields", "id,name,caption")
Example code
Squiggly.init(mapper, new RequestSquigglyContextProvider("fields", "id,name,caption") {
#Override
protected String customizeFilter(String filter, HttpServletRequest request, Class beanClass) {
if (filter != null && Page.class.isAssignableFrom(beanClass)) {
filter = "**,content[" + filter + "]";
}
return filter;
}
});

Debugging AJAX to Spring Controller (count of variables and spelling)

I am spending a lot of effort debugging ajax calls. The common issues are
number of parameters dont match from the ajax to the controller
spelling of the #RequestMapping parameter does not match
If the type does not match the call happens and it can be debugged. But the bigger issue is the above two. I have 88 parameters that I am passing and have a hell of a time trying to figure out what is missing or spelt incorrectly.
example
#RequestMapping("/saveClient")
public #ResponseBody String saveClientAJAXMethodView(#RequestParam(value = "clientName") String clientName,
.... 88 parameters more
$
.ajax({
type : "Post",
url : "saveClient",
data : {
clientName : clientName,
... 88 parameters more
I got this error
The request sent by the client was syntactically incorrect.
So I changed the signature of my controller to add , method = RequestMethod.POST).
Now I am getting
message Request method 'GET' not supported
description The specified HTTP method is not allowed for the requested resource.
Its clearly a "POST" and still it get a request method GET not supported.
The question is NOT how to solve this problem. The question is how to debug such issues easily. What errors map to what issues, how to debug the 88 parameter spellings and count ? There must be a easier way to do this debugging.
I use the following
Debugging Mode of the controller
Inspect on Chrome.
Since you are sending huge amount of parameter in URL for POST request. I would suggest you to send your data in body.
For example if you are sending parameters like clientId, clientName, clientEmail etc.. you have used #RequestParam annotation to get individual parameter data in your controller:
String saveClientAJAXMethodView( #RequestParam String clientId,
#RequestParam String clientName,
#RequestParam String clientEmail
.... more parameters)
Instead of using #RequestParam I would suggest you to use #RequestBody, For this you need to create a Data transfer object (DTO) like this:
class ClientInfo{
String clientId,
String clientName,
String clientEmail,
....
.... other variables
.... getters and setters of variables
}
And then use this DTO in your controller method like this:
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
}
Using this approach you will not get any exception regarding spelling mistake or parameter missing .The value will be assigned to a DTO variable if you are sending value with right key as specified in DTO.
To count variables in ClientInfo object you will need to cast ClientInfo to JSONObject and use its size() method to get count of variables
String saveClientAJAXMethodView(#RequestBody ClientInfo clientInfo){
JSONObject json = new JSONObject(clientInfo);
System.out.println(json.keySet().size());
}
Your ajax call will look like this:
var clientInfo = {
'clientName': 'tom',
'clientId': '23AZ1',
'clientEmail': 'xyz#gmail.com',
...
};
$.ajax({
url: url,
type: "POST",
data: JSON.stringify(clientInfo),
contentType: "application/json",
complete: callback
});
I hope following steps would help you debug:
1- Use a filter to intercept request.
2- Create a custom annotation which would indicate that you want to debug this method.
3- Use the method defined in this post Can I get all of requestMapping URL with GET method in the Spring? and your custom annotation to store list of all methods which you want to debug in a singleton bean.
4- Now write some logic in filter which would print mismatch between the method parameters and request parameters.
CustomFilter:
public class CustomFilter extends GenericFilterBean {
#Autowired
#Qualifier("printMismatchMethods")
HashMap<String,Method> methodsToCheck;
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
HttpServletRequest r = (HttpServletRequest) request;
String url = r.getRequestURI().substring(r.getContextPath().length());
//Remove extensions if present any
int index = url.indexOf('.');
if(index > 0)
url = url.substring(0,url.indexOf('.'));
/*Matching string this should be replaced by url pattern matching of spring.*/
if (methodsToCheck.containsKey(url)){
Method method = methodsToCheck.get(url);
Map<String, String[]> requestParameterMap = r.getParameterMap();
Map<String,Boolean> isParamPresent = new HashMap<String,Boolean>();
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
if (!requestParam.name().isEmpty())
isParamPresent.put(requestParam.name(), false);
else
isParamPresent.put(requestParam.value(), false);
}
}
for (Parameter parameter : method.getParameters()){
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null && requestParam.required()){
String name = null;
if (!requestParam.name().isEmpty())
name=requestParam.name();
else
name=requestParam.value();
if (requestParameterMap.containsKey(name)){
isParamPresent.put(name, true);
}
}
}
for (Map.Entry<String, Boolean> entry : isParamPresent.entrySet()){
if (!entry.getValue()){
System.out.println(entry.getKey() + " is either missing or mis-spelled");
}
}
}
chain.doFilter(request, response);
}
}
configured as follows:
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
Declare following custom annotation.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface PrintParamMismatch {
}
Define following bean which would populate on startup
#Bean(name="printMismatchMethods")
#Autowired
public HashMap<String,Method> printParamMismatchMethods(BeanFactory beanFactory){
HashMap<String,Method> methods = new HashMap<String,Method>();
Map<String, RequestMappingHandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(
(ListableBeanFactory)beanFactory,
RequestMappingHandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
ArrayList<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(handlerMappings);
RequestMappingHandlerMapping mappings = matchingBeans.get("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mappings.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerMethod : handlerMethods.entrySet()){
RequestMappingInfo info = handlerMethod.getKey();
HandlerMethod hMethod = handlerMethod.getValue();
Method method = hMethod.getMethod();
if (method.getAnnotation(PrintParamMismatch.class) != null){
String path = info.getPatternsCondition().toString();
path = path.substring(1,path.length());
path = path.substring(0,path.length()-1);
methods.put(path, method);
}
}
}
return methods;
}
This, I think is generic enough to show debug information for now. However we need to store and match Patterns instead of url string.
So I used divide and rule and solved my issue. I commented top half of the parameters and ran, to check if my controller gets called. It did. then I added 1/4th, then 1/8th and found that I missed a parameter.
That along with #ArsianAnjum's answer is good for debugging. #Aji's answer is the long term solution. I should be using that.

Use #PageableDefault with Spring Data REST

The documentation of #PageableDefault says:
Annotation to set defaults when injecting a
org.springframework.data.domain.Pageable into a controller method.
When using Spring Data REST, is there a way to set default values without defining a controller ?
Setting PageableDefault in the repository like below doesn't seem to work.
Page<Player> findAll(#PageableDefault(size=5) Pageable pageable);
Solution for Spring and Spring-Boot
You may extend RepositoryRestConfigurerAdapter configuration to set the default page size:
#Configuration
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration repositoryRestConfiguration) {
repositoryRestConfiguration.setDefaultPageSize(5);
}
}
Solution for Spring-Boot only
You may set the default size in application.properties:
spring.data.rest.default-page-size=5
Other Spring Data properties:
# DATA REST (RepositoryRestProperties)
spring.data.rest.base-path= # Base path to be used by Spring Data REST to expose repository resources.
spring.data.rest.default-page-size= # Default size of pages.
spring.data.rest.detection-strategy=default # Strategy to use to determine which repositories get exposed.
spring.data.rest.enable-enum-translation= # Enable enum value translation via the Spring Data REST default resource bundle.
spring.data.rest.limit-param-name= # Name of the URL query string parameter that indicates how many results to return at once.
spring.data.rest.max-page-size= # Maximum size of pages.
spring.data.rest.page-param-name= # Name of the URL query string parameter that indicates what page to return.
spring.data.rest.return-body-on-create= # Return a response body after creating an entity.
spring.data.rest.return-body-on-update= # Return a response body after updating an entity.
spring.data.rest.sort-param-name= # Name of the URL query string parameter that indicates what direction to sort results.
source: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#appendix
When using RestResource annotated repository methods, one can customize the page size by intercepting the request and adding a default parameter value (if none present):
#Component
#Order(1)
public class RestResourceDefaultPaginationFilter implements Filter {
#Value("${rest.resource.custom.page.size}")
private String pageSize;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new HttpServletRequestWrapper((HttpServletRequest) request) {
public String getParameter(String paramName) {
String value = super.getParameter(paramName);
// if no size parameter defined on request, then use the configuration default
if ("size".equals(paramName) && StringUtils.isEmpty(value)) {
return page.size;
}
return value;
}
}, response);
}
}
Then register this filter for specific url patterns:
#Configuration
public class RestResourcePaginationConfig {
#Bean
public FilterRegistrationBean<RestResourceDefaultPaginationFilter> paginationFilter() {
FilterRegistrationBean<RestResourceDefaultPaginationFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new RestResourceDefaultPaginationFilter());
registrationBean.addUrlPatterns("/myEntities/search/rest-resource-endpoint");
return registrationBean;
}
}
This can come handy when you want to have exceptions to the more general option described by alexbt.

Jersey get credentials from url

I have an url like this:
http://log:pass#localhost:8080/myendpoint
And Jersey endpoint:
#GET #Produces(MediaType.APPLICATION_JSON) #Path("/login")
#Consumes(MediaType.APPLICATION_JSON) public Response login(
#Context HttpHeaders headers, #QueryParam("callback") String callback)
{
}
And ideally I want to get 'log' and 'pass' in my endpoint method. How to do that in Jersey? I tried many endpint method signatures, filters, etc but it shows me
http://localhost instead of http://log:pass#localhost everywhere
How to achieve this?
Assuming that in your front end, you are sending your parameters as a json object using JSON.stringify() then back in your endpoint method. Add this as a second argument to that method signature JsonObject payload. Then you can access your query parameters within that method as follows
String log = payload.getString("log");
String pass = payload.getString("pass");
Revised Version
#Path("/login")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response login(#Context UriInfo uriInfo, #Context HttpHeaders headers, #QueryParam("callback") String callback) {
URI requestUri = uriInfo.getRequestUri();
String authority = requestUri.getAuthority(); // authority contains what you need;
}

Spring MVC Controller method mapping using form body

I'm building a small application to serve as a client for some third party library here at work. The API states that a Webhookis needed to respond some asynchronous events, but all their methods have the very same signature, apart from a changing _method field between the calls. For example, I have a _method = ping, media, etc.
I'd like to have separate methods on my controller to respond for each one of these methods. If the app allowed me to specify different URLs for each method it would be easy to use Spring MVC's #RequestMapping for each one of them. But I have to specify a single endpoint to receive all calls.
Is there a way (for example using Spring's HttpMessageConverter or something like that) to map different controller methods based on what the Request Body is? I've already tried with #RequestBody, #RequestParam but didn't seem to find anything.
I really, really didn't want to use a bunch of case, switch methods on a front controller to dispatch actions based on my _method field that comes with my POST data, so I happen to believe someone had this problem before and solved it intelligently.
Thanks a lot!
Edit 1: Providing source code
#Controller
#RequestMapping("/webhooks")
public class WebhookController {
#RequestMapping(method = RequestMethod.POST, params = {"_method=ping"})
#ResponseBody
public String ping(){
return "pong";
}
#RequestMapping(method = RequestMethod.POST, params = {"_method=media"})
#ResponseBody
public String media(){
return "media";
}
}
This is the answer:
{
"timestamp": 1440875190389,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.UnsatisfiedServletRequestParameterException",
"message": "Parameter conditions \"_method=ping\" not met for actual request parameters: ",
"path": "/webhooks"
}
Right, I got it working. The answer is a bit tricky so I wanted to register it here should anyone have such problem.
#Neil McGuigan pointed me on the right direction on his comment but I didn't pay attention at first. The main culprit here is a very, very, very bad API design on our remote application's side.
_method is a field used to specify non-standard HTTP verbs such as PUT, PATCH, DELETE, TRACE and so on. This field is filtered by HiddenHttpMethodFilter and the HttpServletRequest is wrapped with this 'new' method. You can see at the file's source how it works.
As I wanted this _method field to get thru the filter without modifying the whole request (and causing the errors because there's no such verb as pingor message on `RequestMethod) I firstly had to deactivate the filter. This could be done by two ways:
I could stop Spring Boot from automagically configuring Spring MVC, skipping WebMvcAutoConfiguration from being loaded when the ApplicationContext was loaded. As you can imagine this is a BIG, BIG, BIIIIG NO because, well, things could happen.
I could use a FilterRegistrationBean to disable the bad filter. Pretty simple and straightforward, this was the method I chose to use:
#Bean
public FilterRegistrationBean registration(HiddenHttpMethodFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
Last but not least, I decided to give HiddenHttpMethodFilter a little extension to somehow improve how the requests were getting thru. The Java EE Spec is pretty clear on the Servlet Spec Commandments where it states:
Thou should not alter your request on your side. You must respect the sender (something like that)
Though I agree with this, for the sake of my mental stability I decided to alter it anyway. To achieve this, we can use a simple HttpServletRequestWrapper, override the chosen methods and filter the original request with the wrapped part. I ended up doing something like this:
public class WhatoolsHiddenHttpMethodFilter extends OrderedHiddenHttpMethodFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String paramValue = request.getParameter(OrderedHiddenHttpMethodFilter.DEFAULT_METHOD_PARAM);
if("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
List<String> whatoolsMethods = Arrays.asList("ping", "message", "carbon", "media", "media_carbon", "ack");
if(whatoolsMethods.contains(paramValue)){
WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
.HttpMethodRequestWrapper(request, "POST", paramValue);
filterChain.doFilter(wrapper, response);
} else {
WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
.HttpMethodRequestWrapper(request, method, null);
filterChain.doFilter(wrapper, response);
}
} else {
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
private final String whatoolsMethod;
public HttpMethodRequestWrapper(HttpServletRequest request, String method, String whatoolsMethod) {
super(request);
this.method = method;
this.whatoolsMethod = whatoolsMethod;
}
#Override
public String getMethod() {
return this.method;
}
#Override
public String getHeader(String name) {
if("x-whatools-method".equals(name)){
return this.whatoolsMethod;
}
return super.getHeader(name);
}
#Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
if(this.whatoolsMethod != null){
names.add("x-whatools-method");
}
return Collections.enumeration(names);
}
}
}
So, what this does is to wrap the request with a new x-whatools-method header when the header is in my whatoolsMethods list. With this, I can easily use #RequestMapping's headers property and map the requests to the correct controller methdods.
Back to the initial question, I'm almost sure (well, 99,95% should be completely sure but let's not risk it) the params property on #RequestMapping works only for request parameters on GET URIs, e.g http://foo.bar/?baz=42. It won't work filtering parameters sent on the request's body.
Thanks Neil for your guidance, even if small! I hope this helps someone.
You can use params in a request mapping:
#RequestMapping(value="/foo", params={"_method=ping"})
Assuming these are post parameters that is
params DOES work for POST, I promise you
Here's my controller:
#Controller
#RequestMapping("/test1")
public class ParamTestController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody String getA(){
return "A";
}
#RequestMapping(method = RequestMethod.POST, params = {"b"})
#ResponseBody String getB(){
return "B";
}
}
Here's my test:

Resources