I have many #RequestParam over many controllers that I need to enforce to be read as lower-case (e.g. #RequestParam String email). Is there an easier way other than going to each endpoint and setting the value of the parameter to the lower-case version?
What I tried is creating a Filter that passes a HttpServletRequestWrapper which overrides getParameter and passes it down the chain, but I discovered that #RequestParam have nothing to do with getParameter(), maybe Spring uses reflection here?
Any suggestions?
I would go with the #InitBinder and PropertyEditorSupport provided for java beans. If I understand correctly, you want all the #RequestParam parameters to be lowercase. For this to achieve, I would declare my own StringLowerCaseEditor classs as below
public class StringLowerCaseEditor extends PropertyEditorSupport
{
#Override
public String getAsText()
{
return getValue().toString();
}
#Override
public void setAsText( String text ) throws IllegalArgumentException
{
setValue( text.toLowerCase() );
}
}
And then, within the #Controller class, a method can be used with the annotation #InitBinder which process all the request coming to this controller. From spring's doc about the annotation
Such init-binder methods support all arguments that RequestMapping
supports, except for command/form objects and corresponding validation
result objects. Init-binder methods must not have a return value; they
are usually declared as void.
#InitBinder
public void initBinder( WebDataBinder dataBinder )
{
StringLowerCaseEditor lowerCaseEditor = new StringLowerCaseEditor();
dataBinder.registerCustomEditor( String.class, lowerCaseEditor );
}
Or, to have a global impact, add the #InitBinder Method to #ControllerAdvice class. This will get executed on calling for every controller. You have the fine grained controll on the packages that needs this also via #ControllerAdvice(basePackages = "" )
#ControllerAdvice
public class AllControllerAdvice
{
#InitBinder
public void initBinder( WebDataBinder dataBinder )
{
StringLowerCaseEditor lowerCaseEditor = new StringLowerCaseEditor();
dataBinder.registerCustomEditor( String.class, lowerCaseEditor );
}
}
If you want to process only a specific #RequestParam, you can use the #InitBinder with the expected param.
#InitBinder( "customerEmail" ) // Request param, can be comma separated
public void initBinder( WebDataBinder dataBinder )
{
StringLowerCaseEditor lowerCaseEditor = new StringLowerCaseEditor();
dataBinder.registerCustomEditor( String.class, lowerCaseEditor );
}
Related
I am writing a spring boot command line tool that is supposed to interface with an API backend I already implemented. That API backend is built with spring data rest with the hateoas package, so it produces HAL message types.
In my CLI tool, I want to POST an entity that contains a list of other entities (one to many relation). For easier use, I wanted to use Resource types in the models to express relations and have a JSON serializer to transform the Resources into only their self hrefs.
My serializer works fine for one to one relations, but never gets calls to serialize arrays or any collection types.
This is what the API accepts when I POST an entity:
{
"property1": "value1",
"myrelation" : "http://localhohst:8080/relatedentities/1"
"mycollection": [
"http://localhost:8080/otherrelatedentities/2",
"http://localhost:8080/otherrelatedentities/3"
]
}
On the CLI side, I created a model entity in the CLI application like this:
#Getter #Setter
public class MyEntity {
private String property1;
#JsonSerialize(using = HateoasResourceIdSerializer.class)
private Resource<RelatedEnity> myrelation;
#JsonSerialize(using = HateoasResourceIdSerializer.class)
private List<Resource<OtherRelatedEntity>> mycollection;
}
I wrote this HateoasResourceIdSerializer to transform any Resource type into only its self href:
public class HateoasResourceIdSerializer extends StdSerializer<Resource<?>> {
private static final long serialVersionUID = 1L;
public HateoasResourceIdSerializer() {
this(null);
}
public HateoasResourceIdSerializer(Class<Resource<?>> t) {
super(t);
}
#Override
public void serialize(Resource<?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(value.getId().getHref());
}
}
Looking at the payload sent to the API backend, I can see that the "myrelation" property is set to the URL of the target entity while the "mycollection" property is always null.
I tried writing a 2nd Serializer that would accept Collection<Resource<?>> but that didnt get called either.
My expectation would be that the serializer above for Resource would be applied to arrays as well as any collection type.
EDIT:
I was asked to provide code to register serializers, so here it is. I added the two mixins as suggested in one of the answers below (hope I did it right) but did not see the expected behavior. I also assumed that due to the registration I could remove the #JsonSerialize(using = HateoasResource(s)IdSerializer.class) annotation from the properties. The current behavior is that those properties do not get rendered at all.
#SuppressWarnings("deprecation")
#SpringBootApplication
#EnableHypermediaSupport(type=EnableHypermediaSupport.HypermediaType.HAL)
public class Application extends WebMvcConfigurerAdapter implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(SwissArmyKnifeApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// ...
}
#Autowired
private HalHttpMessageConverter halHttpMessageConverter;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(halHttpMessageConverter);
super.configureMessageConverters(converters);
}
}
#Configuration
public class HalHttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public HalHttpMessageConverter() {
super(new ObjectMapper(), new MediaType("application", "hal+json", DEFAULT_CHARSET));
objectMapper.registerModule(new Jackson2HalModule());
objectMapper
.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(new DefaultRelProvider(), null, null));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.addMixIn(Resource.class, ResourceMixIn.class);
objectMapper.addMixIn(Resources.class, ResourcesMixIn.class);
}
#Override
protected boolean supports(Class<?> clazz) {
return ResourceSupport.class.isAssignableFrom(clazz);
}
}
You need to properly register your custom serialiser using MixIn feature. Instead of annotating property, you need to annotate class which informs Jackson you want to use it in all scenarios, not only for MyEntity class.
Create MixIn interface:
#JsonSerialize(using = HateoasResourceIdSerializer.class)
interface ResourceMixIn {
}
And register it:
ObjectMapper mapper = JsonMapper.builder()
.addMixIn(Resource.class, ResourceMixIn.class).build();
See other questions how to configure Jackson mapper in Spring:
How do i use Jackson Json parsing in a spring project without any annotations?
Different JSON configuration in a Spring application for REST and Ajax serialization
Spring Boot custom serializer for Collection class
Spring Boot Jackson date and timestamp Format
You are not including code to indicate how you are registering serializer for your type so that could give the clue. But custom serializers definitely should be called for array, Collection and Map values as well as simple property values.
Registering separate serializer for Collection<Type> is not needed (and is actually bit trickier to do: possible, but more work due to nested type) and is not meant to be done just to support specific type in collection (but rather to support more special Collections, if any).
So please include code related to registration, as well as version of Jackson used (and obv. if not recent one, consider upgrading it first to see problem still persists).
I want to measure time of sql execution which will be run by MyBatis (Spring Boot project) and bind that with other request parameters, so I can get full info about performance issues regarding specific requests. For that case I have used MyBatis Interceptor on following way:
#Intercepts({
#Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
#Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryMetricsMybatisPlugin implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
Stopwatch stopwatch = Stopwatch.createStarted();
Object result = invocation.proceed();
stopwatch.stop();
logExectionTime(stopwatch, (MappedStatement) invocation.getArgs()[0]);
return result;
}
}
Now when it come to binding with request, I want to store those metrics in request as attribute. I have tried this simple solution to get request, but that was not working since request was always null (I have read that this solution won't work in async methods, but with MyBatis Interceptor and its methods I think that's not the case):
#Autowired
private HttpServletRequest request;
So, the question is how properly get request within MyBatis interceptor?
One important note before I answer your question: it is a bad practice to access UI layer in the DAO layer. This creates dependency in the wrong direction. Outer layers of your application can access inner layers but in this case this is other way round. Instead of this you need to create a class that does not belong to any layer and will (or at least may) be used by all layers of the application. It can be named like MetricsHolder. Interceptor can store values to it, and in some other place where you planned to get metrics you can read from it (and use directly or store them into request if it is in UI layer and request is available there).
But now back to you question. Even if you create something like MetricsHolder you still will face the problem that you can't inject it into mybatis interceptor.
You can't just add a field with Autowired annotation to interceptor and expect it to be set. The reason for this is that interceptor is instantiated by mybatis and not by spring. So spring does not have chance to inject dependencies into interceptor.
One way to handle this is to delegate handling of the interception to a spring bean that will be part of the spring context and may access other beans there. The problem here is how to make that bean available in interceptor.
This can be done by storing a reference to such bean in the thread local variable. Here's example how to do that. First create a registry that will store the spring bean.
public class QueryInterceptorRegistry {
private static ThreadLocal<QueryInterceptor> queryInterceptor = new ThreadLocal<>();
public static QueryInterceptor getQueryInterceptor() {
return queryInterceptor.get();
}
public static void setQueryInterceptor(QueryInterceptor queryInterceptor) {
QueryInterceptorRegistry.queryInterceptor.set(queryInterceptor);
}
public static void clear() {
queryInterceptor.remove();
}
}
Query interceptor here is something like:
public interface QueryInterceptor {
Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}
Then you can create an interceptor that will delegate processing to spring bean:
#Intercepts({
#Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }),
#Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) })
public class QueryInterceptorPlugin implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
QueryInterceptor interceptor = QueryInterceptorRegistry.getQueryInterceptor();
if (interceptor == null) {
return invocation.proceed();
} else {
return interceptor.interceptQuery(invocation);
}
}
#Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
#Override
public void setProperties(Properties properties) {
}
}
You need to create an implementation of the QueryInterceptor that does what you need and make it a spring bean (that's where you can access other spring bean including request which is a no-no as I wrote above):
#Component
public class MyInterceptorDelegate implements QueryInterceptor {
#Autowired
private SomeSpringManagedBean someBean;
#Override
public Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
// do whatever you did in the mybatis interceptor here
// but with access to spring beans
}
}
Now the only problem is to set and cleanup the delegate in the registry.
I did this via aspect that was applied to my service layer methods (but you can do it manually or in spring mvc interceptor). My aspect looks like this:
#Aspect
public class SqlSessionCacheCleanerAspect {
#Autowired MyInterceptorDelegate myInterceptorDelegate;
#Around("some pointcut that describes service methods")
public Object applyInterceptorDelegate(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
QueryInterceptorRegistry.setQueryInterceptor(myInterceptorDelegate);
try {
return proceedingJoinPoint.proceed();
} finally {
QueryInterceptorRegistry.clear();
}
}
}
I want to produce HTTP Response Body with an error message referencing something like _"missing ... 'CUSTOM_AUTHORITY'"_ in addition to a 403 Forbidden HTTP Status code.
My application is Spring Boot with a Spring-Security-Secured #PreAuthorize method within a Spring-MVC-REST #Controller:
MyController
#Controller
#RequestMapping("/foo")
public FooController{
#PreAuthorize("hasAuthority('CUSTOM_AUTHORITY')")
public Object getSomething(){ ... }
}
GlobalExceptionHandlerResolver
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(AccessDeniedException.class)
#ResponseStatus(HttpStatus.FORBIDDEN)
public Object forbidden(AccessDeniedException exception){ ... }
}
What I want is to expose/inject Collection<ConfigAttribute>. The Spring Security docs reference it.
There doesn't seem to be a straightforward way of accomplishing this. The AccessDecisionManager (which is AffirmativeBased) throws the AccessDeniedException with none of the information you want. So if you want to "expose/inject" the Collection<ConfigAttribute>, you'll want to provide your own AccessDecisionManager that throws a custom exception that holds the ConfigAttributes.
The easiest way to do this could be to wrap the default AccessDecisionManager with your own and delegate method calls to it:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled=true)
CustomMethodSecurityConfig extends GlobalMethodSecurityConfiguration
#Override
protected AccessDecisionManager accessDecisionManager() {
AccessDecisionManager default = super.accessDecisionManager();
MyCustomDecisionManager custom = new CustomDecisionManager(default);
}
}
You could define your custom AccessDecisionManager as follows:
public class MyCustomDecisionManager implements AccessDecisionManager {
private AccessDecisionManager default;
public MyCustomDecisionManager(AccessDecisionManager acm) {
this.default = acm;
}
#Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException{
try {
default.decide(authentication, object, configAttributes)
} catch(AccessDeniedException ex) {
throw new CustomAccessDeniedException(ex.getMessage(), configAttributes);
}
}
// other methods delegate to default
}
Now whenever access is denied, you will get an exception that holds the Collection<ConfigAttribute>.
Your custom exception could look like this:
public class CustomAccessDeniedException extends AccessDeniedException {
private Collection<ConfigAttribute> attributes;
public CustomAccessDeniedException(String message, Collection<ConfigAttribute> attr) {
super(message);
this.attributes = attr;
}
public Collection<ConfigAttribute> getAttributes() {
return this.attributes;
}
}
Now your #ExceptionHandler could handle your CustomAccessDeniedException and have access to the ConfigAttributes.
HOWEVER...
I am not sure that will provide you with the error message you wanted. The ConfigAttribute interface only has one method:
String getAttribute();
And the javadoc states:
If the ConfigAttribute cannot be expressed with sufficient precision as a String, null should be returned.
Since we can't rely on the interface method, how you deal with each ConfigAttribute will be heavily dependent on the type of the particular object you're dealing with.
For example, the ConfigAttribute that corresponds to #PreAuthorize("hasAuthority('CUSTOM_AUTHORITY')") is PreInvocationExpressionAttribute, and to print something that resembles what you want, you could do:
PreInvocationExpressionAttribute attr = (PreInvocationExpressionAttribute)configAttribute;
String expressionString = attr.getAuthorizeExpression().getExpressionString();
System.out.println(expressionString); // "hasAuthority('CUSTOM_AUTHORITY')"
That's the major drawback. Also, you would get ALL the ConfigAttributes, not necessarily the ones that failed.
I am using spring AOP to check permission
#Component
#Aspect
public class PermissionManager {
#Around(value = "#annotation(requiredPermission) && args(id,..)", argNames = "id,requiredPermission")
public Object checkCanViewFile(ProceedingJoinPoint pjp, String id, RequiredPermission permission) throws Throwable {
...
}
}
Controller
#RequiredPermission(RequiredPermission.OperationType.editProject)
#RequestMapping("/searchFile")
public #ResponseBody
WebFile search(String id, String word) throws TokenExpiredException, FetchException {
...
}
It works on spring mvc test but can not working on real environment. the value of 'id' is null, I doubt spring AOP get this method before jackson objectmapper, is it right? How can fix it?
Is it possible to use both JSR-303 bean validation and traditional validation (a single validator class for the type) in Spring? If so, what configuration is required to set this up?
I have tried the instructions on the reference.
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new DualEntryValidator());
}
#RequestMapping(value="/dualEntry.htm", method = RequestMethod.POST)
public ModelAndView handlePost(#Valid DualEntryForm form, BindingResult result) {
ModelAndView modelAndView = new ModelAndView("dualEntry", getCommonModel());
if (!result.hasErrors()){
//do logic
return modelAndView;
}else {
modelAndView.addObject("dualEntryForm", form);
return modelAndView;
}
}
I can get this to use my custom Validator or the JSR-303 validation, but not both. If I have the initBinder present as in the example it uses the custom Validator. If I remove it the JSR-303 bean validation is used. How can I use both?
I've done that following the instructions here:
http://blog.jteam.nl/2009/08/04/bean-validation-integrating-jsr-303-with-spring/
See the "Enjoy both worlds" section. Shortly, you explicitly run a JSR303 validation from a Spring validator, "joining" the results of JSR303 validations based on annotations and your custom validation logic.
I realise this is quite old, but I got this to work with minimal disturbance to my code
Change binder.setValidator(new DualEntryValidator());
to
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new DualEntryValidator());
}
With setValidator() you're replacing the JSR-303 validator with your one. With addValidator(), the JSR-303 validator is called and so is yours.
You need to make sure that your validator does not overlap with your JSR-303 #NotNull, #Min, #Max, etc. annotations otherwise you'll get duplicate error messages added.
Spring provides three handle for bean validation.
1.abstract class AbstractPropertyValidationAnnotationHandler
2.abstract class AbstractMethodValidationAnnotationHandler
3.abstract class ClassValidationAnnotationHandler
In this example i am implementing custom annotation CustomAnnotationHandle
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
Class CustomAnnotationHandle extends Annotation{
public abstract String value();
}
To implement custom annotation for property validation we need to extend AbstractPropertyValidationAnnotationHandler Class.
AbstractPropertyValidationAnnotationHandler provides createValidationRule abstract method
protected abstract AbstractValidationRule createValidationRule(Annotation annotation, Class class1, String s);
So,the extended class must provide implementation of
protected abstract AbstractValidationRule createValidationRule(Annotation annotation, Class class1, String s)
public class CustomPropertyAnnotationHandler extends AbstractPropertyValidationAnnotationHandler
{
public CustomPropertyAnnotationHandler()
{
super(new Class[] {
XXX.XXX.PackageLevle.CustomAnnotationHandle // as it takes array of custom annotation ,so we can pass more than one
// overwriting abstract method
protected AbstractValidationRule createValidationRule(Annotation annotation, Class class1, String s){
CustomAnnotationHandle value = (CustomAnnotationHandle)annotation;
return TestValidationRule(value.getValue());
// as you can see it return AbstractValidationRule.So, we need a class to give our bean specific validation rule.In our case it is
//TestValidationRule
}
}
}
public class TestValidationRule extends AbstractValidationRule
{
public TestValidationRule (String valuetest)
{
super();
this.valuetest = valuetest;
}
Private String valuetest;
}
Spring provides AnnotationBeanValidationConfigurationLoader Class.This class is used for spring own annotation for bean validation.
DefaultValidationAnnotationHandlerRegistry class is used as defaultHandlerRegistry.But if we need to provide our own annotaion then we
need to extend AnnotationBeanValidationConfigurationLoader and set our specific handleregistry via method
setHandlerRegistry(new CustomPropertyAnnotationHandler());
Class DefaultValidationAnnotationHandlerRegistry is used to register spring own annotation for bean validation.It register bean by
calling registerPropertyHandler method of SimpleValidationAnnotationHandlerRegistry class.So for our custom annotation we need to
register CustomPropertyAnnotationHandler by calling registerPropertyHandler method of SimpleValidationAnnotationHandlerRegistry class
public class OurBeanSpecificValidationLoader extends AnnotationBeanValidationConfigurationLoader
{
public OurBeanSpecificValidationLoader ()
{
super();
setHandlerRegistry(new OurSpecificAnnotationHandleRegistery ());
}
}
public class OurSpecificAnnotationHandleRegistery extends DefaultValidationAnnotationHandlerRegistry
{
public OurSpecificAnnotationHandleRegistery ()
{
registerPropertyHandler(new CustomPropertyAnnotationHandler() );
}
}
so you have your custom annotation for bean valiation.E.g
#CustomAnnotationHandle(value = "test")
private Object test;