I'm having below api
#GetMapping(value = "/employees")
public List<Employee> getEmployees(
#RequestParam(value = "mode", required = false) final EmployeeMode mode) {
//calling service from here
}
I'm having EmployeeMode enum as requestParam.
public enum EmployeeMode {
REGULAR,
ALL,
TEMPROARY
}
I want to accept request with case insensitive. Tried with #JsonAlias, #JsonCreator and objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true); and spring.jackson.mapper.accept-case-insensitive-enums: true. nothing worked for me.
I'm using spring boot 2.5.5.
How to accept case insensitive request with requestParam? And if requestParam is empty/null, want to set default enum as ALL.
You can handle it by implementing converter.
public class EmployeeModeConverter implements Converter<String, EmployeeMode> {
#Override
public EmployeeMode convert(String source) {
switch (source.toUpperCase()) {
case "REGULAR": return EmployeeMode.Regular;
case "TEMPROARY": return EmployeeMode.TEMPROARY;
default: return EmployeeMode.ALL;
}
}
}
#Configuration
public class Config extends WebMvcConfigurationSupport {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new EmployeeModeConverter());
}
}
Related
I have a Spring Boot application where an interface has a constraint:
#Constraint(
validatedBy = {
MyValidator.class
})
public #interface MyInterface {
...
}
I'm using Togglz to enable/disable some features and one class where I want to implement some Togglz code is in MyValidator.
public class MyValidator
implements MyInterface<
MyInterface, TDLDetails> {
private FeatureManager featureManager;
public static final Feature FEATURE_ONE =
new NamedFeature("FEATURE_ONE ");
public MyValidator(FeatureManager featureManager) {
this.featureManager = featureManager;
}
#Override
public void initialize(MyInterface arg0) {}
#Override
public boolean isValid(TDLDetails tdlDetails, ConstraintValidatorContext ctx)
{
if (!featureManager.isActive(FEATURE_ONE)) {
if (tdlDetails.getType().equals(TDLType.ANA)) {
return (tdlDetails.getPlaceOfIssue() != null);
}
}
return true;
}
}
Am I wrong to have the parameterized constructor? It seems I need it for Togglz but I'm not sure how it should be used by #Constraint if it takes a parameter. What's the correct way to do this?
I am working within an environment that changes credentials every several minutes. In order for beans that implement clients who depend on these credentials to work, the beans need to be refreshed. I decided that a good approach for that would be implementing a custom scope for it.
After looking around a bit on the documentation I found that the main method for a scope to be implemented is the get method:
public class CyberArkScope implements Scope {
private Map<String, Pair<LocalDateTime, Object>> scopedObjects = new ConcurrentHashMap<>();
private Map<String, Runnable> destructionCallbacks = new ConcurrentHashMap<>();
private Integer scopeRefresh;
public CyberArkScope(Integer scopeRefresh) {
this.scopeRefresh = scopeRefresh;
}
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!scopedObjects.containsKey(name) || scopedObjects.get(name).getKey()
.isBefore(LocalDateTime.now().minusMinutes(scopeRefresh))) {
scopedObjects.put(name, Pair.of(LocalDateTime.now(), objectFactory.getObject()));
}
return scopedObjects.get(name).getValue();
}
#Override
public Object remove(String name) {
destructionCallbacks.remove(name);
return scopedObjects.remove(name);
}
#Override
public void registerDestructionCallback(String name, Runnable runnable) {
destructionCallbacks.put(name, runnable);
}
#Override
public Object resolveContextualObject(String name) {
return null;
}
#Override
public String getConversationId() {
return "CyberArk";
}
}
#Configuration
#Import(CyberArkScopeConfig.class)
public class TestConfig {
#Bean
#Scope(scopeName = "CyberArk")
public String dateString(){
return LocalDateTime.now().toString();
}
}
#RestController
public class HelloWorld {
#Autowired
private String dateString;
#RequestMapping("/")
public String index() {
return dateString;
}
}
When I debug this implemetation with a simple String scope autowired in a controller I see that the get method is only called once in the startup and never again. So this means that the bean is never again refreshed. Is there something wrong in this behaviour or is that how the get method is supposed to work?
It seems you need to also define the proxyMode which injects an AOP proxy instead of a static reference to a string. Note that the bean class cant be final. This solved it:
#Configuration
#Import(CyberArkScopeConfig.class)
public class TestConfig {
#Bean
#Scope(scopeName = "CyberArk", proxyMode=ScopedProxyMode.TARGET_CLASS)
public NonFinalString dateString(){
return new NonFinalString(LocalDateTime.now());
}
}
Can I change the default definition from 'default' to my own one. I would like the page to load and instead of it loading the 'default' it would load mine which is just called 'swagger' in this case:
I am using Spring fox and Spring boot. This is my Swagger Config class:
#Configuration
#EnableSwagger2WebMvc
#Import(SpringDataRestConfiguration.class)
public class SwaggerDocumentationConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.openet.usage.trigger"))
.paths(PathSelectors.any())
.build();
}
private static Predicate<String> matchPathRegex(final String... pathRegexs) {
return new Predicate<String>() {
#Override
public boolean apply(String input) {
for (String pathRegex : pathRegexs) {
if (input.matches(pathRegex)) {
return true;
}
}
return false;
}
};
}
#Bean
WebMvcConfigurer configurer () {
return new WebMvcConfigurerAdapter() {
#Override
public void addResourceHandlers (ResourceHandlerRegistry registry) {
registry.addResourceHandler("/config/swagger.json").
addResourceLocations("classpath:/config");
registry
.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
};
}
}
It is possible to change this behavior, but it looks more like a hack.
The SwaggerResourcesProvider is responsible for providing info for the dropdown list. First, implement this interface. Second, add the Primary annotation to your class to become the main implementation that should be used instead of the default InMemorySwaggerResourcesProvider class. But it still makes sense to reuse definitions provided by InMemorySwaggerResourcesProvider and that is why it should be injected.
The last part is to implement the overridden get method and change to the list you want to display. This example should display only one definition named swagger.
// other annotations
#Primary
public class SwaggerDocumentationConfig implements SwaggerResourcesProvider {
private final InMemorySwaggerResourcesProvider resourcesProvider;
#Inject
public MySwaggerConfig(InMemorySwaggerResourcesProvider resourcesProvider) {
this.resourcesProvider = resourcesProvider;
}
#Override
public List<SwaggerResource> get() {
return resourcesProvider.get().stream()
.filter(r -> "swagger".equals(r.getName()))
.collect(Collectors.toList());
}
// the rest of the configuration
}
I just did a redirect in my controller:
#RequestMapping(value = "/", method = RequestMethod.GET)
public void redirectRootToSwaggerDocs(HttpServletResponse response) throws IOException {
response.sendRedirect("/my-api/swagger-ui.html?urls.primaryName=swagger");
}
The easiest way I found is just to make the groupName rank highly alphabetically. Such as "1 swagger", "a swagger" or "-> swagger".
...
return new Docket(DocumentationType.OAS_30)
.groupName("-> swagger");
...
...
return new Docket(DocumentationType.OAS_30)
.groupName("<what u want>")
...
just set a default group name.
I'm trying to use the Spring Validator and #Validated annotation to validate a Get Request parameter but cannot get the validator to run. I'm using a ModelAttribute to try and get the validator to run on the Path Variable instead of the Request Body. Is it possible to run a validator on a Get Request Path Variable?
Here is my controller class and method
#RestController
public class ProfileController {
#RequestMapping(value = "/profile/{param}", method = RequestMethod.GET)
public IVRProfile getProfile(#Validated(ParamValidator.class) #ModelAttribute("param") String param) {
return sampleProfile();
}
#ModelAttribute("param")
public String paramAsModelAttribute(#PathVariable String param) {
return param;
}
}
And the Validator class
#Component
public class ParamValidator implements Validator
{
#Override
public boolean supports(Class<?> clazz)
{
System.out.println("Validator supports test");
return String.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors)
{
System.out.println("Validator Test");
// Validation code
}
}
Neither prints statements are executed when hitting the endpoint.
Any help on what I could be missing or do differently would be greatly appreciated, thanks!
You can implement desired validation functionality as following.
public class ParamValidator implements ConstraintValidator<ParamConstraint, String> {
#Override
public void initialize(ParamConstraint paramConstraint) {
}
#Override
public boolean isValid(String paramField, ConstraintValidatorContext cxt) {
//Perform paramField validation
return true;
}
}
-
#Documented
#Constraint(validatedBy = ParamValidator.class)
#Target( { ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface ParamConstraint {
String message() default "Default validation message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
-
#RequestMapping(value = "/profile/{param}", method = RequestMethod.GET)
public IVRProfile getProfile(#Valid #ParamConstraint #ModelAttribute("param") String param) {
return sampleProfile();
}
And finally don't forget to annotate Controller with #Validated.
#RestController
#Validated
public class ProfileController {
//...
}
More details you can find in the example as mentioned here.
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
You need to added an #Validated annotation to Controller class and any validation annotation before path variable field
If you want to get single RequestParams like status, you can force it by following the code below.
#RestController
public class ProfileController {
#RequestMapping(value = "/profile/{param}", method = RequestMethod.GET)
public IVRProfile getProfile(#RequestParam(name = "status", required = true) String status, #ModelAttribute("param") String param) {}
}
if you want to force PathVariable, then do this.
#RestController
public class ProfileController {
#RequestMapping(value = "/profile/{param}", method = RequestMethod.GET)
public IVRProfile getProfile(#PathVariable(name = "param", required = true) String param, #ModelAttribute("param") String param) {}
}
Hope this work!!!
I am trying to implement pagination to my Spring Data JPA repository in Spring Boot but I am stuck with the following exception when running uni tests:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.Pageable]: Specified class is an interface
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
...
Could someone point out to me what am I missing here? This is my repository:
#Repository
public interface VenueRepository extends PagingAndSortingRepository<Venue, Long> {
public Page<Venue> findAll(Pageable pageable);
}
and controller:
#RestController
#RequestMapping("/venues")
public class VenueController {
#Autowired
private VenueRepository venueRepo;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Page<Venue>> getVenues(Pageable pageable) {
return new ResponseEntity<>(venueRepo.findAll(pageable), HttpStatus.OK);
}
}
and finally my test:
#Test
public void responseOkVenuesTest() throws Exception {
mvc.perform(get("/venues").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk());
}
I spent couple of hours trying to make this work and am running out of ideas. Thank you for any tips!
Change your method getVenues in the way that you can pass the parameters to instantiate a PageRequest instead of passing Pageable :
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<Venue>> getVenues(int from,int to) {
return new ResponseEntity<>(
venueRepo.findAll((new PageRequest(from, to)), HttpStatus.OK).getContent();
}
In addition to #SEY_91's answer you might also like to use the following solution inspired with How to remove redundant Spring MVC method by providing POST-only #Valid? and used in my Spring Boot-driven application for long time.
In short, here is an annotation to annotate controller method parameters:
#Target(PARAMETER)
#Retention(RUNTIME)
public #interface PlainModelAttribute {
}
Now, just a method processor that would scan for parameters annotated with #PlainModelAttribute:
public final class PlainModelAttributeMethodProcessor
extends ModelAttributeMethodProcessor {
private final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index;
private PlainModelAttributeMethodProcessor(final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index) {
super(true);
this.index = index;
}
public static HandlerMethodArgumentResolver plainModelAttributeMethodProcessor(final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index) {
return new PlainModelAttributeMethodProcessor(index);
}
#Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.hasParameterAnnotation(PlainModelAttribute.class) || super.supportsParameter(parameter);
}
#Override
protected Object createAttribute(final String attributeName, final MethodParameter parameter, final WebDataBinderFactory binderFactory,
final NativeWebRequest request) {
final TypeToken<?> typeToken = TypeToken.of(parameter.getGenericParameterType());
final Converter<? super NativeWebRequest, ?> converter = index.get(typeToken);
if ( converter == null ) {
throw new IllegalArgumentException("Cannot find a converter for " + typeToken.getType());
}
return converter.convert(request);
}
#Override
protected void bindRequestParameters(final WebDataBinder binder, final NativeWebRequest request) {
final HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if ( !isSafe(resolve(servletRequest.getMethod())) ) {
((ServletRequestDataBinder) binder).bind(servletRequest);
}
}
private static HttpMethod resolve(final String name) {
return HttpMethod.valueOf(name.toUpperCase());
}
private static boolean isSafe(final HttpMethod method)
throws UnsupportedOperationException {
switch ( method ) {
case GET:
case HEAD:
case OPTIONS:
return true;
case POST:
case PUT:
case PATCH:
case DELETE:
return false;
case TRACE:
throw new UnsupportedOperationException();
default:
throw new AssertionError(method);
}
}
}
I don't really remember, but a resolve() method equivalent should be present in Spring Framework somewhere. Note that I use Google Guava TypeToken in order to let the processor be compatible with generic types (since I use models like IQuery<Foo> and IQuery<Bar> in controllers). Now just register the processor:
#Configuration
#EnableWebMvc
public class MvcConfiguration
extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(createModelAttributeMethodProcessor());
}
private static HandlerMethodArgumentResolver createModelAttributeMethodProcessor() {
return plainModelAttributeMethodProcessor(ImmutableMap.of(pageableTypeToken, MvcConfiguration::toPageable));
}
private static final TypeToken<Pageable> pageableTypeToken = new TypeToken<Pageable>() {
};
private static Pageable toPageable(final WebRequest request) {
return new PageRequest(
ofNullable(request.getParameter("page")).map(Integer::parseInt).orElse(0),
ofNullable(request.getParameter("size")).map(Integer::parseInt).orElse(1)
);
}
}
Here is a web request to a Pageable DTO conversion, and the converter must be registered as an argument resolver. So now it's ready to use:
#RestController
#RequestMapping("/")
public class Controller {
#RequestMapping(method = GET)
public String get(#PlainModelAttribute final Pageable pageable) {
return toStringHelper(pageable)
.add("offset", pageable.getOffset())
.add("pageNumber", pageable.getPageNumber())
.add("pageSize", pageable.getPageSize())
.add("sort", pageable.getSort())
.toString();
}
}
A few examples:
/ ⇒ PageRequest{offset=0, pageNumber=0, pageSize=1, sort=null}
/?page=43 ⇒ PageRequest{offset=43, pageNumber=43, pageSize=1, sort=null}
/?size=32 ⇒ PageRequest{offset=0, pageNumber=0, pageSize=32, sort=null}
/?page=22&size=32 ⇒ PageRequest{offset=704, pageNumber=22, pageSize=32, sort=null}