Does Swagger UI support #PathVariable binding? - spring

Currently when I'm testing the Swagger UI for a GET request that binds the "id" path variable to a data object, the dataType of the "id" field is Model, instead of a Long.
For instance, here is the method in the RestController:
#RequestMapping(value = "/{id}", method = GET)
public AwardVO getAwardById(#PathVariable("id") Award award) {
LOG.info("inside the get award method: "+award);
if (award == null) {
throw new AwardNotFoundException();
}
return new AwardVO(award);
}
Here is the resulting documentation:
So when I pass a Long to the input field, I don't receive the desired record. Is this type of binding supported in Swagger, or do I need to just need to do a lookup for the record and pass the PathVariable as a Long?
Version of Swagger: compile "com.mangofactory:swagger-springmvc:0.9.5"
SwaggerConfig:
#Configuration
#EnableSwagger
public class SwaggerConfig extends WebMvcConfigurerAdapter {
private SpringSwaggerConfig springSwaggerConfig;
#Autowired
public void setSpringSwaggerConfig(SpringSwaggerConfig springSwaggerConfig) {
this.springSwaggerConfig = springSwaggerConfig;
}
#Bean
public SwaggerSpringMvcPlugin customImplementation() {
return new SwaggerSpringMvcPlugin(this.springSwaggerConfig).apiInfo(
apiInfo())
.genericModelSubstitutes(ResponseEntity.class)
.includePatterns("/v1/.*", "/register/.*");
}
private ApiInfo apiInfo() {
ApiInfo apiInfo = new ApiInfo("API", "API",
"API terms of service", "email#gmail.com",
"API Licence Type", "API License URL");
return apiInfo;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Thanks.

It seems like it should work as expected if you replace #PathVariable with #ModelAttribute.
To answer your question, yes it does support #PathVariable, but only primitives or strings.

Related

Not able to get error object in JSON format while using #Valid and MessageSource to get display errors in Spring boot

I am currently learning Spring REST and I am trying to build a demo spring boot app. Incase of DTO object has validation error I want to show it as below:
{
"errors": [
{
"code": "first_error_code",
"message": "1st error message"
"field":"field_name"
}
]
}
Where the code in above JSON should display the validation message that I have given in my entity class i.e
#NotEmpty(message = "{name.not.empty}")
String name;
then code should be name.not.empty and message should be taken from messages.properties file.
Now to achieve this, I used several tutorials. Below are the classes:
Main class: (Included MessageSource and LocalValidatorFactoryBean)
#SpringBootApplication
#EnableSwagger2
public class Demo3PathvariableApplication implements WebMvcConfigurer {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
public static void main(String[] args) {
SpringApplication.run(Demo3PathvariableApplication.class, args);
}
/*
* To enable matrix variables, configurePathMatch() method of WebMvcConfigurer
* needs to overriden. Matrix variables are disabled by default and the
* following configuration
*
* urlPathHelper.setRemoveSemicolonContent(false);
*
* should be present in the overriden method to enable the same. see below
* method.
*/
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST");
}
/* For Swagger Document Generation */
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("com.infytel.controller")).paths(PathSelectors.any()).build()
.useDefaultResponseMessages(false);
// To scan for RestControllers from this package and For disabling default
// response messages
}
}
Controller class:
#RestController
#RequestMapping("/customers")
#Api(value = "CustomerController, REST APIs that deal with Customer DTO")
public class CustomerController {
#Autowired
private CustomerService customerService;
#PostMapping(consumes = "application/json")
public ResponseEntity createCustomer(#RequestBody #Valid CustomerDTO customer, Errors errors) {
return ResponseEntity.ok(customerService.createCustomer(customer));
}
}
FieldErrorDTO.java:
public class FieldErrorDTO {
private String errorCode;
private String message;
private String field;
public FieldErrorDTO(String errorCode, String message, String field) {
this.errorCode = errorCode;
this.message = message;
this.field = field;
}
//Getter setter
ValidationErrorDTO.java:
public class ValidationErrorDTO {
private List<FieldErrorDTO> fieldErrors = new ArrayList<>();
public ValidationErrorDTO() {
super();
}
public void addFieldError(String errorCode, String message, String field) {
FieldErrorDTO error = new FieldErrorDTO(errorCode, message, field);
fieldErrors.add(error);
}
public List<FieldErrorDTO> getFieldErrors() {
return fieldErrors;
}
public void setFieldErrors(List<FieldErrorDTO> fieldErrors) {
this.fieldErrors = fieldErrors;
}
}
RestErrorHandler .java
#ControllerAdvice
public class RestErrorHandler {
#Autowired
private MessageSource messageSource;
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MethodArgumentNotValidException.class)
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getCode(), localizedErrorMessage, fieldError.getField());
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
return localizedErrorMessage;
}
}
messages.properties
name.not.empty=Please provide a name.
email.not.valid=Please provide valid email id.
age.adult.only=Age should be more than 18.
Now with all these config, I am able to see below JSON,
{
"fieldErrors": [
{
"errorCode": "NotEmpty",
"message": "Please provide a name.",
"field": "name"
},
{
"errorCode": "Email",
"message": "Please provide valid email id.",
"field": "email"
}
]
}
How do I acheive this requirement, where instead of "errorCode": "NotEmpty", I want show
"errorCode": "name.not.empty"
From CustomerDTO class?
To do so you need to change you processFieldErrors:
First remove "{}" from your anotations:
#NotEmpty(message = "name.not.empty")
String name;
Second:
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError : fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getDefaultMessage(), localizedErrorMessage, fieldError.getField());
}
return dto;
}
And third, change your message.getMessage:
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError.getDefaultMessage(), null, currentLocale);
return localizedErrorMessage;
}
This way you would retrieve the key for the message. In your example it will be :
name.not.empty
Hope this helps

Why Swagger not showing all methods in same class?

i'm trying to use swagger with my code , but not all methods are listing in swagger-ui some methods not show
i am using swagger 2.5.0 version ,and spring boot 2.1.0.RELEASE
my user rest controller
#RestController
#RequestMapping(value = "/rest")
public class UserRestController {
#Autowired
private UserService userService;
#RequestMapping(method = RequestMethod.GET, value = "/users")
public Iterator<User> getUsers() {
return userService.getUsers();
}
#RequestMapping(method = RequestMethod.GET, value = "/user/{id}")
public User getUser(#PathVariable("id") Long id) {
return userService.getUser(id);
}
#RequestMapping(method = RequestMethod.POST, value = "/user")
public User save(#RequestBody User user) {
User userValidation = userService.getUser(user.getId());
if (userValidation != null) {
throw new IllegalAddException("username already used !");
}
return userService.save(user);
}
#RequestMapping(method = RequestMethod.DELETE, value = "/user")
public User delete(#RequestBody User user) {
return userService.save(user);
}
}
and this my config code
#Configuration
#EnableSwagger2
public class SwaggerApi {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("com.social.core.rest")).paths(PathSelectors.ant("/rest/*"))
.build().apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfo("Social API", "Soccial api for gamerz zone.", "API TOS", "Terms of service",
new Contact("yali", "www.social.com", "prg#gmail.com"), "License of API",
"API license URL");
}
}
getUser method not showing in swagger ui , and the method worked when i hit url and already getting data
just three method are showing
I solved this issue by adding more star in paths with me config
paths(PathSelectors.ant("/rest/**"))

spring-data-rest: Validator not being invoked

I am using springboot 2.0.1.RELEASE with spring-data-rest and followed the workaround mentioned here and my Validator is still not being invoked. Here are the details:
ValidatorRegistrar: Workaround for a bug
#Configuration
public class ValidatorRegistrar implements InitializingBean {
private static final List<String> EVENTS;
static {
List<String> events = new ArrayList<String>();
events.add("beforeCreate");
events.add("afterCreate");
events.add("beforeSave");
events.add("afterSave");
events.add("beforeLinkSave");
events.add("afterLinkSave");
events.add("beforeDelete");
events.add("afterDelete");
EVENTS = Collections.unmodifiableList(events);
}
#Autowired
ListableBeanFactory beanFactory;
#Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
#Override
public void afterPropertiesSet() throws Exception {
Map<String, Validator> validators = beanFactory.getBeansOfType(Validator.class);
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
EVENTS.stream().filter(p -> entry.getKey().startsWith(p)).findFirst()
.ifPresent(p -> validatingRepositoryEventListener.addValidator(p, entry.getValue()));
}
}
}
Validator class:
#Component("beforeSaveBidValidator")
public class BeforeSaveBidValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Bid.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Bid bid = (Bid)target;
if (!bid.getAddendaAcknowledged()) {
errors.rejectValue("addendaAcknowledged",
"addendaAcknowledged is not true");
}
}
}
Custom RestController for Bids:
#RestController
#RequestMapping(path = "/bids")
public class BidController {
private BidRepository bidRepository;
#Autowired
public BidController(
BidRepository bidRepository) {
this.bidRepository = bidRepository;
}
#PutMapping("{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
Rest Client Test Code:
Bid bid = new Bid()
...
bid.setAddendaAcknowledged(false)
Map<String, String> uriVariables = new HashMap<String, String>()
uriVariables.put("id", bid.id)
HttpHeaders headers = new HttpHeaders()
headers.setContentType(MediaType.APPLICATION_JSON)
HttpEntity<Bid> entity = new HttpEntity<>(bid, headers)
ResponseEntity<String> response = restTemplate.exchange(
"/bids/{id}", HttpMethod.PUT, entity, Bid.class, bid.id)
// Expected: response.statusCode == HttpStatus.BAD_REQUEST
// Found: response.statusCode == HttpStatus.OK
// Debugger showed that Validator was never invoked.
Any idea what I am missing?
You are trying to use your validator with custom controller, not SDR controller. In this case you can just add it to your controller with #InitBinder annotation:
#RestController
#RequestMapping("/bids")
public class BidController {
//...
#InitBinder("bid") // add this parameter to apply this binder only to request parameters with this name
protected void bidValidator(WebDataBinder binder) {
binder.addValidators(new BidValidator());
}
#PutMapping("/{id}")
public Bid update(#RequestBody #Valid Bid bid) {
return bidRepository.save(bid);
}
}
#Component annotation on your validator is not necessary as well as ValidatorRegistrar class.
How to use validators with SDR controllers you can read in my another answer.

Swagger doesn't display information about methods - SpringBoot

I have an API in Java SpringBoot and I want to document it in Swagger.
I have done the following (I only include classes that contain some code related to Swagger):
Main class
#EnableSwagger2
public class ProvisioningApiApplication {
public static void main(String[] args) {
if (AuthConfigFactory.getFactory() == null) {
AuthConfigFactory.setFactory(new AuthConfigFactoryImpl());
}
SpringApplication.run(ProvisioningApiApplication.class, args);
}
#Bean
public Docket swaggerSpringMvcPluggin() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.apiInfo(apiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
#Component
#Primary
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
setSerializationInclusion(JsonInclude.Include.NON_NULL);
configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
enable(SerializationFeature.INDENT_OUTPUT);
}
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Provisioning API")
.version("0.0.1")
.build();
}
}
Controller
#RestController
#EnableAutoConfiguration
#CrossOrigin
public class RecursoController {
#Autowired
private Configuration configuration;
#Autowired
private TypeSpecService typeSpecService;
#Autowired
private IoTAgentService ioTAgentService;
#Autowired
private OrionService orionService;
#Autowired
private DeviceIdService deviceIdService;
#ApiOperation(value = "Put a device", nickname = "provisionDevice", tags = "Device")
#ApiResponses({
#ApiResponse(code = 200, message = "Ok", response = NewDeviceResponse.class)
})
#RequestMapping(method = RequestMethod.PUT, value = "/devices", consumes = "application/json", produces = "application/json")
public ResponseEntity<NewDeviceResponse> provisionDevice(#RequestBody NewDeviceRequest newDeviceRequest,
#RequestHeader("X-Auth-Token") String oAuthToken) {
// what my method does
}
The documentation results in the following swagger.json file:
{
swagger: "2.0",
info: {
version: "0.0.1",
title: "Provisioning API"
},
host: "localhost:8080",
basePath: "/"
}
As you can see, it only contains the name and the version of API but not the provisionDevice method.
I've tried everything but I can't figure it out what I'm doing bad. What am I missing?
Did you add #Api annotation in your class, where you have your main services?

Spring Social Facebook Template always return same user

I am using Spring Social 2.0.2.RELEASE to provide social login with Facebook. My problem is that Spring Social always return the same first user when I use FacebookTemplate. Here the example:
```
#Autowired
private Facebook facebook;
#RequestMapping(value = "/facebook/login", method = RequestMethod.GET)
public ModelAndView handleFacebookLogin(HttpServletResponse response) {
//always the same user
User profile = facebook.fetchObject("me", User.class, "id", "name", "link", "email");
return new ModelAndView("redirect:/dashboard");
}
```
I also have a Custom ConnectController:
```
#Controller
#RequestMapping("/connect")
public class CustomConnectController extends ConnectController {
#Autowired
public CustomConnectController(ConnectionFactoryLocator connectionFactoryLocator,
ConnectionRepository connectionRepository) {
super(connectionFactoryLocator, connectionRepository);
}
#Override
protected RedirectView connectionStatusRedirect(String providerId, NativeWebRequest request) {
return new RedirectView("/facebook/login");
}
}
```
If a open two browsers and try to login with different users, it always return the first one. My current solution is just copy the entire ConnectController to my app and change the behaviour. It is terrible and I hope that I am making a big mistake.
I had the same issue and solved the problem by creating this class:
#Configuration
public class UniqueSessionUserID extends SocialConfigurerAdapter {
#Override
public UserIdSource getUserIdSource() {
return new UserIdSource() {
#Override
public String getUserId() {
RequestAttributes request = RequestContextHolder.getRequestAttributes();
String uuid = (String) request.getAttribute("_socialUserUUID", RequestAttributes.SCOPE_SESSION);
if (uuid == null) {
uuid = UUID.randomUUID().toString();
}
request.setAttribute("_socialUserUUID", uuid, RequestAttributes.SCOPE_SESSION);
return uuid;
}
};
}
}
Here is a link where it is explained in more detail why this is necessary:
Spring Social Facebook more than one user

Resources