Add (custom) decoder to WebMVC endpoint - java

I have a WebMVC endpoint:
#RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(#PathVariable String id) {
...
}
Here, the provided id should be decoded first. Is it possible to define an annotation which does this "in the background"; that is, prior to calling the endpoint? Something in the lines of:
#RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(#PathVariable #DecodedIdentifier String id) {
...
}
Note the #DecodedIdentifier annotation. I know it does not exists, but it hopefully explains my intent. I know this is possible with Jersey's JAX-RS implementation, but what about Spring's WebMVC?
Here, I am using base64 decoding, but I wondering if I could inject a custom decoder as well.

Although you can use annotations, I recommend you to use a custom Converter for this purpose.
Following your example, you can do something like this.
First, you need to define a custom class suitable to be converted. For instance:
public class DecodedIdentifier {
private final String id;
public DecodedIdentifier(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
}
Then, define a Converter for your custom class. It can perform the Base64 decoding:
public class DecodedIdentifierConverter implements Converter<String, DecodedIdentifier> {
#Override
public DecodedIdentifier convert(String source) {
return new DecodedIdentifier(Base64.getDecoder().decode(source));
}
}
In order to tell Spring about this converter you have several options.
If you are running Spring Boot, all you have to do is annotate the class as a #Component and the auto configuration logic will take care of Converter registration.
#Component
public class DecodedIdentifierConverter implements Converter<String, DecodedIdentifier> {
#Override
public DecodedIdentifier convert(String source) {
return new DecodedIdentifier(Base64.getDecoder().decode(source));
}
}
Be sure to configure your component scan so Spring can detect the #Component annotation in the class.
If you are using Spring MVC without Spring Boot, you need to register the Converter 'manually':
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new DecodedIdentifierConverter());
}
}
After Converter registration, you can use it in your Controller:
#RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(#PathVariable DecodedIdentifier id) {
...
}
There are also other options you can follow. Please, consider read this article, it will provide you further information about the problem.
As a side note, the above mentioned article indicates that you can directly define a valueOf method in the class which will store the result of the conversion service, DecodedIdentifier in your example, and it will allow you to get rid of the Converter class: to be honest, I have never tried that approach, and I do not know under which conditions it could work. Having said that, if it works, it can simplify your code. Please, if you consider it appropriate, try it.
UPDATE
Thanks to #Aman comment I carefully reviewed the Spring documentation. After that, I found that, although I think that the conversion approach aforementioned is better suited for the use case - you are actually performing a conversion - another possible solution could be the use of a custom Formatter.
I already knew that Spring uses this mechanism to perform multiple conversion but I were not aware that it is possible to register a custom formatter based on an annotation, the original idea proposed in the answer. Thinking about annotations like DateTimeFormat, it makes perfect sense. In fact, this approach were previously described here, in Stackoverflow (see the accepted answer in this question).
In your case (basically a transcription of the answer above mentioned for your case):
First, define your DecodedIdentifier annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public #interface DecodedIdentifier {
}
In fact, you can think of enriching the annotation by including, for example, the encoding in which the information should be processed.
Then, create the corresponding AnnotationFormatterFactory:
import java.text.ParseException;
import java.util.Base64;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import org.springframework.context.support.EmbeddedValueResolutionSupport;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.stereotype.Component;
#Component
public class DecodedIdentifierFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory<DecodedIdentifier> {
#Override
public Set<Class<?>> getFieldTypes() {
return Collections.singleton(String.class);
}
#Override
public Printer<?> getPrinter(DecodedIdentifier annotation, Class<?> fieldType) {
return this.getFormatter(annotation);
}
#Override
public Parser<?> getParser(DecodedIdentifier annotation, Class<?> fieldType) {
return this.getFormatter(annotation);
}
private Formatter getFormatter(DecodedIdentifier annotation) {
return new Formatter<String>() {
#Override
public String parse(String text, Locale locale) throws ParseException {
// If the annotation could provide some information about the
// encoding to be used, this logic will be highly reusable
return new String(Base64.getDecoder().decode(text));
}
#Override
public String print(String object, Locale locale) {
return object;
}
};
}
}
Register the factory in your Spring MVC configuration:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new DecodedIdentifierFormatterFactory());
}
}
Finally, use the annotation in your Controllers, exactly as you indicated in your question:
#RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(#PathVariable #DecodedIdentifier String id) {
...
}

You can achieve this implementing a HandlerMethodArgumentResolver:
public class DecodedIdentifierArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(DecodedIdentifier.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String value = webRequest.getParameterValues(parameter.getParameterName())[0];
return Base64.getDecoder().decode(value);
}
}

The problem with a custom HandlerMethodArgumentResolver and #PathVariable or #RequestParam is that it will never get executed, as #PathVariable and #RequestParam have their own resolvers each, which get executed prior to any custom resolvers. What if I want to obfuscate Long id param with Hashids? Then, the parameter has to be passed as a hashed String, get decoded to original Long id value. How do I provide conversion type change?

Related

how to get MicroProfile REST Client annotation in ClientRequestFilter

My RestClient is annotated by a custom annotation and I want to get the annotation value in a ClientRequestFilter.
Here is my MicroProfile RestClient:
#Path("/greetings")
#RegisterRestClient
#MyAnnotation("myValue")
public interface MyRestClient{
#GET
public String hello();
}
I want to get the annotation value in my ClientRequestFilter:
public class MyFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) {
// Here i want to get the MyAnnotation value. i.e "myValue"
}
}
I tried to call the requestContext.getClient().getAnnotations() method but it does not work since requestContext.getClient() is an instance of org.jboss.resteasy.microprofile.client.impl.MpClient
The implementation in question is RESTEasy. I would like to find a way to get this information from both RESTEasy classic and RESTEasy reactive implementations.
Thanks for your help
Here is the MicroProfile REST Client specific way:
#Provider
public class MyFilter implements ClientRequestFilter {
public void filter(final ClientRequestContext clientRequestContext) {
final Method method = (Method) clientRequestContext
.getProperty("org.eclipse.microprofile.rest.client.invokedMethod");
Class<?> declaringClass = method.getDeclaringClass();
System.out.println(declaringClass);
MyAnnotation myAnnotation = declaringClass.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.value());
}
}
which must work in all implementations including RESTEasy (Classic and Reactive) or Apache CXF.
This should work:
import org.jboss.resteasy.client.jaxrs.internal.ClientRequestContextImpl;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.ext.Provider;
#Provider
public class MyFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) {
Class<?> declaringClass = ((ClientRequestContextImpl) requestContext)
.getInvocation()
.getClientInvoker()
.getDeclaring();
MyAnnotation myAnnotation = declaringClass.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.value());
}
}
Just to mention, this is really RESTEasy specific. The class ClientRequestContextImpl comes from the internal RESTEasy package and thus might be subject to change.

Spring RabbitMQ: implement additional validations without custom annotations

In a Spring RabbitMQ project I am looking for a way to programmatically validate an object that has JSR303 annotations (like #NotNull, #Size, etc) while at the same time requires some custom validation logic. I would normally use a ConstraintValidator in combination with a custom Annotation, but the use of custom Annotations is not an option in this case.
I have the following (simplified) class, which is generated by Swagger and therefore cannot be edited:
#ApiModel(description="User")
public class User {
private String name;
#NotNull
#Size(min = 1, max = 6)
public String getName() {
return this.name;
}
...
}
The additional validation logic is encapsulated in a validator:
#Component
public class UserValidator implements org.springframework.validation.Validator {
#Override
public boolean supports(Class<?> aClass) {
return User.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
User user = (User) o;
...
if(!valid) {
errors.reject("some rejection");
}
}
}
The service in which the validation occurs:
#Service
#RequiredArgsConstructor
public class SomeService {
private final javax.validation.Validator validator; // might as well be org.springframework.validation.Validator if that works better
public void someMethod(User user) {
if (!validator.validate(user).isEmpty()) {
// handle invalid user
}
...
}
}
However, the UserValidator is not being invoked. Is there some way to make Spring aware of the UserValidator? I have read some topics on using an InitBinder, however as this is not a web MVC project but a rabbitMQ project I'm not sure whether this can be used.
It is not clear from your description how this is relevant to Spring AMQP, but if you want to use a validator on the listener method level, you should configure it respectively:
#Configuration
#EnableRabbit
public class Config implements RabbitListenerConfigurer {
...
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setValidator(new MyValidator());
}
}
See docs for more info: https://docs.spring.io/spring-amqp/docs/current/reference/html/#rabbit-validation

Spring boot: inject a request-scoped, lazy-initialized custom User object into a controller

I'm building a Spring Boot application to provide a stateless REST API. For security, we're using OAuth 2. My app receives a bearer-only token.
The user's information is stored in our database. I can look it up using the injected Principal in the controller:
#RequestMapping(...)
public void endpoint(Principal p) {
MyUser user = this.myUserRepository.findById(p.getName());
...
}
To avoid this extra line of boilerplate, I would like to be able to inject the MyUser object directly into my controller method. How can I achieve this? (The best I've come up with so far is to create a Lazy, Request-scoped #Bean...but I haven't been able to get it working...)
The Idiomatic Way
The idiomatic way in Spring Security is to use a UserDetailsService or implement your own:
public class MyUserDetailsService implements UserDetailsService {
#Autowired
MyUserRepository myUserRepository;
public UserDetails loadUserByUsername(String username) {
return this.myUserRepository.findById(username);
}
}
And then there are several spots in the Spring Security DSL where this can be deposited, depending on your needs.
Once integrated with the authentication method you are using (in this case OAuth 2.0), then you'd be able to do:
public void endpoint(#AuthenticationPrincipal MyUser myuser) {
}
The Quick, but Less-Flexible Way
It's generally better to do this at authentication time (when the Principal is being ascertained) instead of at method-resolution time (using an argument resolver) as it makes it possible to use it in more authentication scenarios.
That said, you could also use the #AuthenticationPrincipal argument resolver with any bean that you have registered, e.g.
public void endpoint(
#AuthenticationPrincipal(expression="#myBean.convert(#this)") MyUser user)
{
}
...
#Bean
public Converter<Principal, MyUser> myBean() {
return principal -> this.myUserRepository.findById(p.getName())
}
The tradeoff is that this conversion will be performed each time this method is invoked. Since your app is stateless, this might not be an issue (since the lookup needs to be performed on each request anyway), but it would mean that this controller could likely not be reused in other application profiles.
You can achieve this by implementing HandlerMethodArgumentResolver.
For example:
Custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface Version {
}
Implementation:
public class HeaderVersionArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(Version.class) != null;
}
#Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request
= (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getHeader("Version");
}
}
When you implement this you should add this as argument resolver:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new HeaderVersionArgumentResolver());
}
}
Now we can use it as argument
public ResponseEntity findByVersion(#PathVariable Long id, #Version String version)

Autowired gives Null value in Custom Constraint validator

I am totally new to Spring and I have looked in to a few answers on SO for the asked problem. Here are the links:
Spring 3.1 Autowiring does not work inside custom constraint validator
Autowiring a service into a validator
Autowired Repository is Null in Custom Constraint Validator
I have a Spring project in which I want to use Hibernate Validator for an object validation. Based on what I read online and a few forums I tried to inject validator as follows:
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean().getValidator();
}
But wherever I was using
#Autowired
Validator validator;
It was taking Spring's validator implementation instead of the Hibernate's validator. I couldn't figure out how to exactly inject Hibernate validator and simply Autowire it across other classes so I used a cheap trick, now my Java Config looks like this
#Bean
public Validator validator() {
// ValidatorImpl is Hibernate's implementation of the Validator
return new ValidatorImpl();
}
(I would really appreciate if someone can actually point me into the right direction on how to avoid getting Hibernate Validator in this Hacky way)
But lets come to the main issue here:
Here is custom validation definition
#Target( { METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER } )
#Retention(RUNTIME)
#Constraint(validatedBy = EmployeeValidator.class)
#Documented
public #interface EmployeeValidation {
String message() default "{constraints.employeeConstraints}";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends Payload>[] payload() default {};
}
My Custom Validator
public class EmployeeValidator implements ConstraintValidator<EmployeeValidation , Object> {
#Autowired
private EmployeeService employeeService;
#Override
public void initialize(EmployeeValidation constraintAnnotation) {
//do Something
}
#Override
public boolean isValid(String type, ConstraintValidatorContext context) {
return false;
}
}
In the above Custom Constraint Validator I get the employeeService null. I know that any implementations of ConstraintValidator are not instantiated when Spring is starting up but I thought adding the ValidatorImpl() will actually fix that. But it didn't.
Now I am stuck with a really hacky workaround and I do not want to continue with a code like that. Can someone please help me with my situation.
P.S. These are my imports in the Java Config file:
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
I hope the solution will help someone:
#Bean
public Validator validator () {
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure().constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
Initializing the validator with SpringConstraintValidatorFactory so that injection works and providing the validator implementation to be Hibernate.class works in the following manner:
Your objects will be validated by the library of your choice
Your custom validators will be able to use Spring's functionality while having validation to be executed by Hibernate.
How it works:
Hibernate's ConstraintValidatorFactory does not initialize any ConstraintValidators unless they are called but SpringConstraintValidatorFactory does by giving AutowireCapableBeanFactory to it.
EDIT
As mentioned in one of the comments by #shabyasaschi To inject autowireCapableBeanFactory you can change the method signature as:
Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) {
or add getter and setter for it in the config file as follows:
public AutowireCapableBeanFactory getAutowireCapableBeanFactory() {
return autowireCapableBeanFactory;
}
public void setAutowireCapableBeanFactory(AutowireCapableBeanFactory autowireCapableBeanFactory) {
this.autowireCapableBeanFactory = autowireCapableBeanFactory;
}
You can fix this with two aproaches:
Try to inject Services on your validator using Spring.
Initialize it manually overriding Validator's initialize method.
I had the same problem time ago and finally i decided to use second option avoiding tons of problems.
As you point you must define one initialize method on your validator and there you can use a ServiceUtils to get the service bean you need:
#Autowired
private EmployeeService employeeService;
#Override
public void initialize(EmployeeValidation constraintAnnotation) {
//Use an utility service to get Spring beans
employeeService = ServiceUtils.getEmployeeService();
}
And ServiceUtils is a normal Spring bean with a static reference to itself used in the static methods.
#Component
public class ServiceUtils {
private static ServiceUtils instance;
#Autowired
private EmployeeService employeeService;
/* Post constructor */
#PostConstruct
public void fillInstance() {
instance = this;
}
/*static methods */
public static EmployeeService getEmployeeService) {
return instance.employeeService;
}
}
So you are using Spring to inject the services you need but not in the usual way.
Hope this helps.
In your bean definition
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean().getValidator();
}
What's the type of Validator in the method definition? You should make sure it returns javax.validation.Validator, not Validator from Spring.
Letting Spring bootstrap the validator will it also cause to pass a SpringConstraintValidatorFactory to Hibernate Validator which will enable dependency injection within constraint validators.
There is nothing wrong with your code It depends how are you creating your ValidatorFactory.
Create a bean and let Spring handle it.
#Bean
public ValidatorFactory validatorFactory(){
return Validation.buildDefaultValidatorFactory();
}
Think about it. There should have been no issue with using the #Autowired inside a Constraint validator class. This means that something is wrong.
This issue has been reported on various platform and I have not seen a good solution. I have seen some workaround though.
Here is what I found.
You may notice that the validation is happening twice. the first time it should work but the second time you got a null related error message. The problem should be that the entity or the class that you is being validated is being used twice in your controller. For example, you may want validate the entity class and try to save it at the same time in the same method in the controller method. when you try to save the entity, it will try to validate the object again and this time the #Autowired object will be null.
Here is what you can do for this scenario
You can use dto to carry the validation annotation and copy the property of the dto class to your entity class before you save it into the database. your scenario may be different but the solution approach should be the same.
Below is an illustration of code that works
public ResponseEntity<InstitutionModel> create(#Valid #RequestBody InstitutionDto institutiondto) {
Institution institution = new Institution();
BeanUtils.copyProperties(institutiondto, institution);
return Optional.of(this.institutionService.save(institution)).map(institutionModelAssembler::toModel)
.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
}
private XXXService xxxService = SpringContextHolder.getBean(XXXService.class);
Thats worked for me. For guys who search at now
public class EmployeeValidator implements ConstraintValidator<EmployeeValidation , Object> {
private EmployeeService employeeService;
public EmployeeValidator(EmployeeService employeeService){
this.employeeService = employeeService;
}
...
}

Intercept all responses in Play 2.1

Is there a way to intercept all HTTP responses in using Play Framework 2.1?
This is what I have in my Global.java file to intercept all requests, but I'm also looking to intercept responses:
import java.lang.reflect.Method;
import play.GlobalSettings;
import play.mvc.*;
import play.mvc.Http.*;
import views.html.*;
public class Global extends GlobalSettings {
private static BasicAuthHandler AUTH;
#SuppressWarnings("rawtypes")
#Override
public Action onRequest(Request request, Method actionMethod) {
if ( ... ) {
return new Action.Simple() {
#Override
public Result call(Context ctx) throws Throwable {
return unauthorized();
}
};
}
return super.onRequest(request, actionMethod);
}
}
I've read the documentation on manipulating the response but it only describes how to do it for each result individually.
TransactionalAction is an example of request/response interceptor. It extends Action and provides Transactional annotation which targets controller type or method.
Example of controller method annotated with action:
#Transactional
public static Result ok(){
return ok();
}
More details.
An example of action logging responses (mind, actions which do not provide annotations like Transactional, extend Action.Simple):
public class LogAction extends Action.Simple {
#Override
public F.Promise<Result> call(Http.Context ctx) throws Throwable {
F.Promise<Result> call = delegate.call(ctx);
return call.map(r -> {
String responseBody = new String(JavaResultExtractor.getBody(r, 0L));
Logger.info(responseBody);
return r;
});
}
}
Usage, method definition:
#With(LogAction.class)
public static Result ok(){
return ok();
}
Usage, class definition - all methods intercepted:
#With(LogAction.class)
public class BaseController extends Controller {
....
}
You can go one step forward, if you dont like #With annotation. Define custom annotation yourself:
#With({ LogAction.class })
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface Log {
}
and use it this way:
#Log
public static Result ok(){
return ok();
}
If your custom annotation accepts parameters, change LogAction definition this way:
public class LogAction extends Action<Log> {
// use configuration object to access your custom annotation configuration
}

Categories

Resources